﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;
using System.IO;
using System.Text.RegularExpressions;
using System.Security.Permissions;
using System.Reflection;
using System.Windows.Forms;

using Styx;
using Styx.Common;
using Styx.CommonBot;
using Styx.CommonBot.Inventory;
using Styx.CommonBot.POI;
using Styx.CommonBot.Routines;
using Styx.Helpers;
using Styx.Pathing;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
namespace FTW
{
    public class FightThisWay : CombatRoutine
    {
        #region Class variables
        static FTWConfig Form = new FTWConfig();
        public static string ClassAndSpec = null;
        private static Dictionary<String, FakeCooldown> fakecooldowns = null;
        private static Dictionary<string, DateTime> spellWasLastCast = new Dictionary<string, DateTime>();
        public static string _datapath = Styx.CommonBot.Routines.RoutineManager.RoutineDirectory + "\\FTW\\data";
        private static Color _textcolor = Color.Aqua;
        private static DateTime nextrest = DateTime.Now;
        public static bool UseScrollLock = true;

        public override sealed string Name { get { return "FightThisWay v1.0 by Kamilche"; } }
        public override bool WantButton { get { return true; } }
        public override WoWClass Class { get { return StyxWoW.Me.Class; } }
        public static Dictionary<String, String> rules = new Dictionary<String, String>();
        public static MyBaseClass mbc = null;
        private static bool firstpulse = true;
        public static HashSet<String> AOESpells;
        public static HashSet<String> Stuns;
        public static HashSet<String> TankOnlySpells;
        public static HashSet<String> CastOver;
        public static HashSet<String> IgnoreMobsBattleground;
        public static HashSet<String> IgnoreCanCast;
        public static string[] PropertyNames;
        public static Dictionary<String, int> MobWeights;
        private static string redtext = "";
        private double startgold = 0;
        private DateTime starttime = DateTime.Now;
        private DateTime nextmoney = DateTime.Now;

        #endregion

        private double GetMoney()
        {
            List<string> known = Lua.GetReturnValues("return GetMoney()", "GetMoney.lua");
            double gold = (ulong.Parse(known[0])) / 10000;
            return gold;
        }


        public override void Initialize()
        {
            startgold = GetMoney();
            log("Starting gold: {0:0.00}", startgold);

            ClassAndSpec = StyxWoW.Me.ClassAndSpec();

            // Check the pathname
            if (!File.Exists(_datapath + "\\Spell_Overrides.txt"))
            {
                string newpath = Styx.CommonBot.Routines.RoutineManager.RoutineDirectory + "\\fightthisway\\data";
                if (File.Exists(newpath + "\\Spell_Overrides.txt"))
                    _datapath = newpath;
                else
                {
                    string s = string.Format("Could not find path:\n\n{0}\n\nMake sure you have installed this software to the following path:\n\n    HonorBuddy\\Routines\\FTW", _datapath);
                    MessageBox.Show(s, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    throw new Exception(s);
                }
            }


            // Load the rules
            LoadRules();

            // Watch for changes to the rules
            string filename = _datapath;
            FileSystemWatcher fsm = new FileSystemWatcher(filename);
            fsm.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            fsm.Filter = "*.txt";
            fsm.Changed += new FileSystemEventHandler(OnChanged);
            fsm.Created += new FileSystemEventHandler(OnChanged);
            fsm.Deleted += new FileSystemEventHandler(OnChanged);
            fsm.Renamed += new RenamedEventHandler(OnChanged);
            fsm.EnableRaisingEvents = true;
            mbc = new MyBaseClass();
            DungeonWatcher.Initialize();
        }

        public static void LoadRules()
        {
            fakecooldowns = LoadFakeCooldowns(_datapath + @"\Spell_Overrides.txt");
            AOESpells = LoadList(_datapath + @"\Spell_Target_Circle.txt");
            Stuns = LoadList(_datapath + @"\Spell_Stuns.txt");
            TankOnlySpells = LoadList(_datapath + @"\Spell_Tank_Only.txt");
            CastOver = LoadList(_datapath + @"\Spell_Cast_Over.txt");
            IgnoreMobsBattleground = LoadList(_datapath + @"\Ignore_Mobs_Battleground.txt");
            PropertyNames = File.ReadAllLines(_datapath + @"\Properties.txt");
            IgnoreCanCast = LoadList(_datapath + @"\Spell_Ignore_CanCast.txt");
            MobWeights = LoadWeights(_datapath + @"\Mob_Weights.txt");
            try
            {
                log("Loading rules for {0}", ClassAndSpec);
                string filename = _datapath + "\\" + ClassAndSpec + ".txt";
                rules[ClassAndSpec] = File.ReadAllText(filename);
            }
            catch (Exception ex)
            {
                log(Color.Red, "Couldn't find a data file for {0}! {1}", ClassAndSpec, ex.Message);
            }
            if (mbc != null)
                mbc.Initialize();
        }

        private static Dictionary<String, FakeCooldown> LoadFakeCooldowns(String filename)
        {
            string[] readText = File.ReadAllLines(filename);
            Dictionary<String, FakeCooldown> lst = new Dictionary<String, FakeCooldown>();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < readText.Length; i++)
            {
                string line = readText[i];
                if (line.StartsWith("//") || line.Length < 5)
                    continue;
                FakeCooldown fc = new FakeCooldown(line);
                sb.AppendLine(String.Format("    {0}\t{1}\t{2}", fc.SpellID, fc.Cooldown, fc.Name));
                if (lst.ContainsKey(fc.Name))
                    throw new Exception(string.Format("Spell {0} entered in {1} twice!", fc.Name, filename));
                lst.Add(fc.Name, fc);
            }
            return lst;
        }

        private static HashSet<String> LoadList(String filename)
        {
            HashSet<String> h = new HashSet<String>();
            string[] lines = File.ReadAllLines(filename);
            foreach (String line in lines)
            {
                String s = line.Trim();
                if (s.Length == 0 || s.StartsWith("//") || s.StartsWith("--"))
                    continue;
                if (h.Contains(s))
                    throw new Exception(string.Format("Line '{0}' entered in file '{1}' twice!", s, filename));
                h.Add(s);
            }
            return h;
        }

        private static Dictionary<String, int> LoadWeights(String filename)
        {
            Dictionary<String, int> h = new Dictionary<String, int>();
            string[] lines = File.ReadAllLines(filename);
            foreach (String line in lines)
            {
                String s = line.Trim();
                if (s.Length == 0 || s.StartsWith("//") || s.StartsWith("--"))
                    continue;
                String[] words = s.Split('\t');
                if (h.ContainsKey(words[1]))
                    throw new Exception(string.Format("Line '{0}' entered in file '{1}' twice!", s, filename));
                h[words[1]] = Int32.Parse(words[0]);
            }
            return h;
        }

        private static void OnChanged(object source, FileSystemEventArgs e)
        {
            rules.Clear();
        }

        public static bool ValidState()
        {
            if (StyxWoW.IsInGame &&
                StyxWoW.Me != null &&
                StyxWoW.Me.IsValid &&
                !StyxWoW.Me.IsDead &&
                !StyxWoW.Me.IsGhost &&
                (!StyxWoW.Me.IsFlying || !StyxWoW.Me.IsActuallyInCombat) &&
                !StyxWoW.Me.IsOnTransport &&
                !StyxWoW.Me.OnTaxi)
            {
                if (StyxWoW.Me.Mounted)
                {
                    if (Battlegrounds.IsInsideBattleground)
                        return true;
                    else if (Styx.Helpers.LevelbotSettings.Instance.GroundMountFarmingMode)
                        return false;
                    else
                        return true;
                }
                else
                    return true;
            }
            else
                return false;
        }

        public static bool PetInCombat()
        {

            if (StyxWoW.Me.GotAlivePet &&
                StyxWoW.Me.PetInCombat &&
                StyxWoW.Me.Pet.CurrentTarget.IsValidUnit() &&
                StyxWoW.Me.Pet.CurrentTarget.Combat)
                return true;
            else
                return false;
        }

        public static bool MeOrPartyMemberInCombat()
        {
            // PetInCombat always returns true now
            if (mbc.adds.Count > 0 || StyxWoW.Me.IsActuallyInCombat || PetInCombat())
            {
                //log("Adds count: {0}, me in combat: {1}, pet in combat: {2}", mbc.adds.Count, StyxWoW.Me.IsActuallyInCombat, StyxWoW.Me.PetInCombat);
                return true;
            }
            else
                return false;
        }

        #region Honorbuddy Methods

        public override void OnButtonPress()
        {
            new FTWConfig().ShowDialog();
        }

        private static void UseTravelForm(object sender, MountUpEventArgs e)
        {
            float traveldist = 30;
            float dist = StyxWoW.Me.Location.Distance(e.Destination);
            string errmsg = string.Empty;
            while (SpellManager.GlobalCooldown || StyxWoW.Me.IsCasting || StyxWoW.Me.ChanneledSpell != null)
                Thread.Sleep(10);
            if (!SpellManager.HasSpell("Travel Form"))
                errmsg = "Don't have travel form.";
            else if (e.Destination == WoWPoint.Zero)
                errmsg = "Destination is WoWPoint.Zero";
            else if (dist > traveldist)
                errmsg = string.Format("Dist {0} > traveldist {1}", dist, traveldist);
            else if (Battlegrounds.IsInsideBattleground && DateTime.Now < Battlegrounds.BattlefieldStartTime)
                errmsg = "Battle hasn't started yet.";
            if (errmsg != string.Empty)
            {
                log("Not using travel form because {0}", errmsg);
            }
            else
            {
                e.Cancel = true;
                log("Using travel form for point {0} yards away (MountDistance: {1})",
                    dist,
                    Styx.Helpers.CharacterSettings.Instance.MountDistance);
                if (!StyxWoW.Me.HasAura("Travel Form"))
                    SpellManager.Cast("Travel Form");
            }
        }



        public override void Pulse()
        {
            string fnname = "FTW.Pulse";
            MyTimer.Start(fnname);

            // Display startup message
            if (firstpulse == true)
            {
                firstpulse = false;
                // Eh, I don't like it. Uncomment if you really want it.
                //Mount.OnMountUp += UseTravelForm;
                mbc.Initialize();
                log("FTW starting at {0}", DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss"));
                log("Current bot: {0}", Styx.CommonBot.BotManager.Current.Name);
            }

            // Unstick ourselves if necessary.
            CheckForStuck.Pulse();

            // Display last red text
            if (StyxWoW.LastRedErrorMessage != redtext)
            {
                redtext = StyxWoW.LastRedErrorMessage;
                //log(Color.Red, "Red text: {0}", redtext);
            }

            // Leave early if not in valid state
            if (!ValidState())
            {
                // do nothing
            }
            else
            {
                // Save current class and spec
                ClassAndSpec = StyxWoW.Me.ClassAndSpec();

                // List aura changes
                //AuraWatcher.Pulse();

                // Load rules if they're not loaded
                if (!rules.ContainsKey(ClassAndSpec))
                    LoadRules();

                mbc.Pulse();

                if (MeOrPartyMemberInCombat())
                {
                    //if (BotPoi.Current.Type == PoiType.Loot)
                    //{
                    //    debug("Not done with combat, clearing loot flag");
                    //    BotPoi.Clear();
                    //}
                    // Force combat because me OR party/raid members in combat)
                    Combat();
                }
            }

            // Perform dungeon stuff
            DungeonWatcher.Pulse();

            // Perform UI stuff
            UI.Pulse();

            // Perform money calculation
            if (DateTime.Now > nextmoney)
            {
                nextmoney = DateTime.Now.AddSeconds(10);
                double currentgold = GetMoney();
                double elapsedtime = (DateTime.Now - starttime).TotalHours;
                if (elapsedtime > 0)
                {
                    log("Starting gold: {0:0.00}. Current gold: {1:0.00} Gold per hour: {2:0.00}",
                        startgold, currentgold, (currentgold - startgold) / elapsedtime);
                }
            }

            MyTimer.Stop(fnname);
            MyTimer.Pulse();


        }

        public override void Combat()
        {
            string fnname = "FTW.Combat";
            MyTimer.Start(fnname);
            if (!ValidState())
            {
                // Do nothing
            }
            else
            {
                CheckForChest.Clear();
                Evade.CheckCombat();
                mbc.EvaluateRules("@COMBAT");
            };
            MyTimer.Stop(fnname);
        }

        public override void Pull()
        {
            string fnname = "FTW.Pull";
            MyTimer.Start(fnname);
            if (!ValidState())
            {
                // do nothing
            }
            else
            {
                CheckForChest.Clear();
                if (Evade.CheckPull())
                    mbc.EvaluateRules("@PULL");
            }
            MyTimer.Stop(fnname);
        }

        public override bool NeedRest
        {
            // Most of the code to be executed is in the text file, in the 'Rest' section.
            // That code is executed in the 'EvaluateRules' call below.
            get
            {
                string fnname = "FTW.NeedRest";
                MyTimer.Start(fnname);
                if (!ValidState())
                {
                    // do nothing
                }
                else
                {
                    if (CharacterSettings.Instance.LootChests)
                        CheckForChest.Pulse();

                    if (DateTime.Now < nextrest)
                    {
                        // do nothing
                    }
                    else
                    {

                        nextrest = DateTime.Now.AddSeconds(3);

                        bool didsomething = mbc.EvaluateRules("@REST");

                        // Wait for eating or drinking to finish.
                        while (StyxWoW.Me.EatingOrDrinking())
                        {
                            Thread.Sleep(10);
                            if ((StyxWoW.Me.HealthPercent > 98 && StyxWoW.Me.ManaPercent > 98) || StyxWoW.Me.IsActuallyInCombat
                                || PetInCombat()
                                || mbc.GetAdds().Count > 0)
                                break;
                        }
                        while (StyxWoW.Me.IsCasting)
                        {
                            int spellid = (int)StyxWoW.Me.CurrentCastId;
                            if (spellid == 0)
                                return false;
                            Thread.Sleep(10);
                        }
                        if (didsomething)
                        {
                            while (StyxWoW.Me.IsChanneling)
                                Thread.Sleep(10);
                        }
                    }
                }
                MyTimer.Stop(fnname);
                return false;
            }
        }

        #endregion

        #region FakeCooldown

        public class FakeCooldown
        {
            public int SpellID;
            public double Cooldown;
            public string Name;
            public FakeCooldown(string line)
            {
                line = line.Replace("\t", " ");
                line = line.Replace("  ", " ");
                string[] words = line.Split(' ');
                this.SpellID = Int32.Parse(words[0]);
                this.Cooldown = Double.Parse(words[1]);
                StringBuilder sb = new StringBuilder();
                for (int j = 2; j < words.Length; j++)
                {
                    sb.Append(words[j]);
                    if (j < words.Length - 1)
                        sb.Append(" ");
                }
                this.Name = sb.ToString();
            }
        }

        #endregion

        #region Logging

        public static void log(string format, params object[] args)
        {
            log(_textcolor, format, args);
        }

        private static string lastmessage = string.Empty;
        public static void log(Color color, string format, params object[] args)
        {
            string s2 = string.Format(format, args);
            if (s2 == lastmessage)
                return;
            lastmessage = s2;
            string s = string.Format("[FTW] {0:hh:mm:ss} ", DateTime.Now) + s2;
            Logging.Write(System.Windows.Media.Color.FromRgb(color.R, color.G, color.B), s);
        }

        public static void debug(string format, params object[] args)
        {
            debug(_textcolor, format, args);
        }

        public static void debug(Color color, string format, params object[] args)
        {
            string s = string.Format(string.Format("[FTW] {0:hh:mm:ss} ", DateTime.Now) + format, args);
            Logging.Write(LogLevel.Diagnostic, System.Windows.Media.Color.FromRgb(color.R, color.G, color.B), s);
        }
        #endregion

        public class MyBaseClass
        {
            #region Class variables
            private string mode = string.Empty;
            private Dictionary<ulong, Dictionary<String, double>> properties;
            private Dictionary<string, int> fakePetCooldownTimes = new Dictionary<string, int>
                {
                    {"Leap", 10},
                    {"Gnaw", 10}
                };
            private WoWUnit tank;
            private WoWUnit healer;
            public List<WoWPlayer> people = new List<WoWPlayer>();
            private List<WoWPlayer> closePeople = new List<WoWPlayer>();
            private WoWPlayer healTarget;
            private WoWPlayer cleanseTarget;
            private WoWPlayer reviveTarget;
            private WoWUnit add;
            private int avgHealth = 100;
            private int _eclipsedirection = 1;
            public string lastspellcastname = string.Empty;
            private DateTime lastspellcasttime = DateTime.Now;
            private Dictionary<string, DateTime> CastOnce = new Dictionary<string, DateTime>();
            private int CastOnceTime = 120;
            public List<WoWUnit> adds = new List<WoWUnit>();
            public List<WoWUnit> nontargetadds = new List<WoWUnit>();
            public static HashSet<WoWDispelType> dispels = new HashSet<WoWDispelType>();
            private static int cliptime = 50;
            private static double range = 4.95;
            private DateTime MovementCooldown = DateTime.Now;
            HashSet<String> manaspellnames = new HashSet<String> { "Restore Mana", "Water Spirit" };
            HashSet<String> healthspellnames = new HashSet<String> { "Healing Potion", "Healthstone" };
            HashSet<String> debugitems = new HashSet<String> { };
            private List<WoWPoint> targetpath = null;
            private WoWPoint lasttargetloc = WoWPoint.Empty;

            #endregion

            #region Pulse

            public MyBaseClass()
            {
                string fnname = "MBC.Constructor";
                MyTimer.Start(fnname);
                if (SpellManager.HasSpell("Detox"))
                {
                    // Monk
                    if (!dispels.Contains(WoWDispelType.Disease)) dispels.Add(WoWDispelType.Disease);
                    if (!dispels.Contains(WoWDispelType.Poison)) dispels.Add(WoWDispelType.Poison);
                }
                if (SpellManager.HasSpell("Remove Corruption"))
                {
                    // Druid
                    if (!dispels.Contains(WoWDispelType.Curse)) dispels.Add(WoWDispelType.Curse);
                    if (!dispels.Contains(WoWDispelType.Poison)) dispels.Add(WoWDispelType.Poison);
                }
                if (SpellManager.HasSpell("Remove Curse"))
                {
                    // Mage
                    if (!dispels.Contains(WoWDispelType.Curse)) dispels.Add(WoWDispelType.Curse);
                }
                if (SpellManager.HasSpell("Purify Spirit"))
                {
                    // Shaman
                    if (!dispels.Contains(WoWDispelType.Curse)) dispels.Add(WoWDispelType.Curse);
                    if (!dispels.Contains(WoWDispelType.Magic)) dispels.Add(WoWDispelType.Magic);
                }
                if (SpellManager.HasSpell("Cleanse"))
                {
                    // Paladin
                    if (!dispels.Contains(WoWDispelType.Disease)) dispels.Add(WoWDispelType.Disease);
                    if (!dispels.Contains(WoWDispelType.Poison)) dispels.Add(WoWDispelType.Poison);
                }
                if (SpellManager.HasSpell("Mass Dispel"))
                {
                    // Priest
                    if (!dispels.Contains(WoWDispelType.Curse)) dispels.Add(WoWDispelType.Curse);
                    if (!dispels.Contains(WoWDispelType.Disease)) dispels.Add(WoWDispelType.Disease);
                    if (!dispels.Contains(WoWDispelType.Magic)) dispels.Add(WoWDispelType.Magic);
                    if (!dispels.Contains(WoWDispelType.Poison)) dispels.Add(WoWDispelType.Poison);
                }
                if (SpellManager.HasSpell("Purify"))
                {
                    // Priest
                    if (!dispels.Contains(WoWDispelType.Disease)) dispels.Add(WoWDispelType.Disease);
                    if (!dispels.Contains(WoWDispelType.Magic)) dispels.Add(WoWDispelType.Magic);
                }
                if (SpellManager.HasSpell("Sacred Cleansing") ||
                    SpellManager.HasSpell("Nature's Cure") ||
                    SpellManager.HasSpell("Improved Cleanse Spirit"))
                {
                    if (!dispels.Contains(WoWDispelType.Magic)) dispels.Add(WoWDispelType.Magic);
                }
                if (dispels.Count > 0)
                {
                    log("Player can dispel the following:");
                    foreach (WoWDispelType d in dispels)
                        log("    {0}", d.ToString());
                }

                MyTimer.Stop(fnname);

            }

            public void Initialize()
            {
                Pulse();
                On_DumpParty();
                EvaluateRules("@INITIALIZE");
            }

            public void Pulse()
            {
                string fnname = "MBC.Pulse";
                MyTimer.Start(fnname);
                try
                {

                    if (!FightThisWay.ValidState())
                    {
                        // Do nothing
                        log(Color.Red, "Not valid state");
                    }
                    else
                    {
                        // Get lists of interesting people

                        // Party or raid members
                        people = GetGroup();

                        // Tank
                        tank = GetTank();

                        // Healer
                        healer = GetHealer();

                        // Party members within range
                        closePeople = people.Where(p => p.DistanceCalc() <= 40).ToList();

                        // Party members within range, in line of sight, that need healing
                        healTarget = GetHealTarget();

                        // Diseased party member
                        // Only tanks and me get dispels, the rest can stay sick.
                        // A necessary optimization to keep the speed up.
                        cleanseTarget = (from unit in closePeople
                                         where unit.Role().Contains("Tank") || unit.Guid == StyxWoW.Me.Guid
                                         where unit.EligibleForCleanse() == true
                                         orderby unit.HealWeight()
                                         select unit).FirstOrDefault();

                        // Dead party member outside combat
                        reviveTarget = (from unit in closePeople
                                        where unit.IsDead || unit.IsGhost
                                        select unit).FirstOrDefault();
                        if (reviveTarget != null)
                            log(Color.Red, "{0} is dead - revive him if you can.", reviveTarget.SafeName());

                        // Get average health of all party members.
                        try
                        {
                            avgHealth = (int)(from p in closePeople where p.IsDead == false select p.HealthPercent).Average();
                        }
                        catch (Exception ex)
                        {
                            avgHealth = (int)StyxWoW.Me.HealthPercent;
                        }

                        // Get mobs fighting us
                        adds = GetAdds();

                        // Get mobs fighting us, that we're not targeting
                        WoWUnit pettarget = null;
                        if (StyxWoW.Me.GotAlivePet)
                            pettarget = StyxWoW.Me.Pet.CurrentTarget;
                        nontargetadds = (from unit in adds
                                         where unit != StyxWoW.Me.CurrentTarget && unit != pettarget
                                         select unit).ToList();

                        // Get a mob fighting us, that we're not targeting.
                        add = nontargetadds.FirstOrDefault();
                    }
                }
                catch (Exception ex)
                {
                    log(Color.Red, ex.ToString());

                }
                MyTimer.Stop(fnname);
            }

            public List<WoWPlayer> GetGroup()
            {
                List<WoWPlayer> list;
                try
                {

                    string fnname = "MBC.GetGroup";
                    MyTimer.Start(fnname);
                    //ulong[] guids = StyxWoW.Me.GroupInfo.RaidMemberGuids.Union(StyxWoW.Me.GroupInfo.PartyMemberGuids).Union(new[] { StyxWoW.Me.Guid }).Distinct().ToArray();

                    //list = (from p in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true)
                    //        where p.IsFriendly && guids.Any(g => g == p.Guid)
                    //        select p).ToList();

                    list = (StyxWoW.Me.GroupInfo.IsInRaid ? StyxWoW.Me.RaidMembers : StyxWoW.Me.PartyMembers);
                    if (!list.Contains(StyxWoW.Me))
                        list.Add(StyxWoW.Me);
                    list.Sort(
                        delegate(WoWPlayer p1, WoWPlayer p2)
                        {
                            if (p1.DistanceSqr.CompareTo(p2.DistanceSqr) == 0)
                                return p1.Guid.CompareTo(p2.Guid);
                            else
                                return p1.DistanceSqr.CompareTo(p2.DistanceSqr);
                        }
                    );

                    MyTimer.Stop(fnname);
                    return list;
                }
                catch (Exception ex)
                {
                    log(Color.Red, "Error when getting group members: {0}", ex.Message);
                    list = new List<WoWPlayer>();
                    list.Add(StyxWoW.Me);
                    return list;
                }
            }

            #endregion

            #region Rules

            public bool EvaluateRules(String desiredsection)
            {
                string fnname = string.Format("MBC.EvaluateRules {0}", desiredsection);
                MyTimer.Start(fnname);

                mode = desiredsection;
                string result = string.Empty;

                List<String> origlines = rules[ClassAndSpec].Split('\n').ToList();

                // Replace left side of conditions with current values
                String s;
                MyTimer.Start("MBC.EvaluateRules.Replace");
                using (StyxWoW.Memory.AcquireFrame())
                {
                    this.StartReplace();
                    s = rules[ClassAndSpec];
                    s = this.Replace("Me", s, StyxWoW.Me);
                    s = this.Replace("Target", s, StyxWoW.Me.CurrentTarget);
                    s = this.Replace("Tank", s, tank);
                    s = this.Replace("Healer", s, healer);
                    s = this.Replace("Heal", s, healTarget);
                    s = this.Replace("Pet", s, StyxWoW.Me.Pet);
                    s = this.Replace("Cleanse", s, cleanseTarget);
                    s = this.Replace("Revive", s, reviveTarget);
                    s = this.Replace("Add", s, add);
                }
                MyTimer.Stop("MBC.EvaluateRules.Replace");

                MyTimer.Start("MBC.EvaluateRules.Remainder");
                List<String> lines = s.Split('\n').ToList();
                lines.Add("@END OF FILE");

                for (int i = 0; i < lines.Count; i++)
                    lines[i] = lines[i].Replace("\n", "").Replace("\r", "");
                for (int i = 0; i < origlines.Count; i++)
                    origlines[i] = origlines[i].Replace("\n", "").Replace("\r", "");

                string section = null;
                string action = null;
                List<string> conditions = new List<string>();
                List<string> output = new List<string>();

                for (int i = 0; i < lines.Count; i++)
                {
                    // Skip blank lines
                    if (lines[i].Replace("\t", "").Trim().Length == 0 || lines[i].Trim().StartsWith("--"))
                        continue;

                    if (section == desiredsection)
                    {
                        // Only process lines in the correct section
                        if (lines[i].StartsWith("\t"))
                        {
                            // New condition
                            conditions.Add(lines[i]);
                            output.Add(string.Format("{0:D3} {1} {2}", i + 1, origlines[i].Replace("\t", "    ").Replace("\"", "'"), lines[i].Replace("\t", "    ").Replace("\"", "'")));
                        }
                        else
                        {
                            // Finish off old action
                            bool ConditionsMatch = true;
                            if (action != null)
                            {
                                foreach (string check in conditions)
                                    if (ConditionsMatch) ConditionsMatch = EvaluateCondition(check, action, i);
                                if (!ConditionsMatch)
                                    result = "SKIPPED";
                                else if (ExecuteAction(action))
                                    result = "TRUE";
                                else
                                    result = "FALSE";

                                output.Add(string.Format("        {0}", result));
                                if (result == "TRUE")
                                {
                                    //foreach (string xx in output)
                                    //    debug(xx);
                                    MyTimer.Stop("MBC.EvaluateRules.Remainder");
                                    MyTimer.Stop(fnname);
                                    return true;
                                }
                                //if (action == "StopCasting")
                                //    foreach (string xx in output)
                                //        debug(xx);

                            }
                            output = new List<string>();
                            conditions.Clear();
                            action = null;

                            // New action
                            if (!lines[i].StartsWith("@"))
                            {
                                action = lines[i];
                                output.Add(string.Format("{0:D3} {1}", i + 1, action));
                            }
                        }
                    }
                    if (lines[i].StartsWith("@"))
                    {
                        // New section
                        section = lines[i];
                    }
                }

                MyTimer.Stop("MBC.EvaluateRules.Remainder");
                MyTimer.Stop(fnname);
                return false;
            }

            private bool EvaluateCondition(string condition, string action, int line)
            {
                string fnname = string.Format("MBC.EvaluateCondition {0}", mode);
                MyTimer.Start(fnname);
                bool retval = true;
                string[] lines;
                string origcondition = condition;
                condition = condition.Replace("\t", "");
                while (condition.StartsWith(" "))
                    condition = condition.Substring(1, condition.Length - 1);
                condition = condition.Replace("  ", " ");
                if (condition.Length < 2)
                {
                    retval = true;
                    goto done;
                }
                lines = condition.Split(new Char[] { ' ' });
                double value1 = 0;
                double value2 = 0;
                if (lines.Length != 3)
                {
                    log(Color.Red, "Invalid condition found when evaluating action {0} line {1} - need 3 words '{2}'", action, line.ToString(), origcondition);
                    retval = false;
                    goto done;
                }
                try
                {
                    value1 = System.Double.Parse(lines[0]);
                }
                catch (Exception ex)
                {
                    log(Color.Red, "Bad number on left side found when evaluating action {0} line {1} - '{2}' ({3})", action, line.ToString(), lines[0], ex.Message);
                    retval = false;
                    goto done;
                }
                string op = lines[1];
                try
                {
                    value2 = System.Double.Parse(lines[2]);
                }
                catch (Exception ex)
                {
                    log(Color.Red, "Bad number on right side found when evaluating action {0} line {1} - '{2}' ({3})", action, line.ToString(), lines[2], ex.Message);
                    retval = false;
                    goto done;
                }
                if (op == "=")
                {
                    if (value1 == value2)
                        retval = true;
                    else
                        retval = false;
                    goto done;
                }
                if (op == "!=" || op == "<>")
                {
                    if (value1 != value2)
                        retval = true;
                    else
                        retval = false;
                    goto done;
                }
                if (op == ">=")
                {
                    if (value1 >= value2)
                        retval = true;
                    else
                        retval = false;
                    goto done;
                }
                if (op == "<=")
                {
                    if (value1 <= value2)
                        retval = true;
                    else
                        retval = false;
                    goto done;
                }
                if (op == ">")
                {
                    if (value1 > value2)
                        retval = true;
                    else
                        retval = false;
                    goto done;
                }
                if (op == "<")
                {
                    if (value1 < value2)
                        retval = true;
                    else
                        retval = false;
                    goto done;
                }
                log(Color.Red, "Unknown operator {0} when processing {1} line {2}!", op, action, line.ToString());
                retval = false;
                goto done;

            done:
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool ImbueWeapon(String spellname, string slotname, int slotid)
            {
                bool retval;
                log(Color.YellowGreen, "ImbueWeapon {0} {1} {2}", spellname, slotname, slotid);
                if (!SpellManager.HasSpell(spellname))
                {
                    log(Color.YellowGreen, "Don't have spell");
                    retval = false;
                }
                else if (OnCooldown(spellname, true))
                {
                    retval = false;
                }
                else
                {
                    log(Color.YellowGreen, "Imbuing {0} weapon with '{1}'", slotname, spellname);
                    Lua.DoString(string.Format("CancelItemTempEnchantment({0})", slotid));
                    Thread.Sleep(1000);
                    On_Cast(string.Format("Me.ImbueWeapon{0}", slotid), spellname, StyxWoW.Me, false, false);
                    Thread.Sleep(1000);
                    SaveCooldown(spellname);
                    retval = true;
                }
                return retval;
            }

            private string WeaponEnchant(int slotid)
            {
                string retval = string.Empty;
                WoWItem weapon;
                if (slotid == 1)
                    weapon = StyxWoW.Me.Inventory.Equipped.MainHand;
                else
                    weapon = StyxWoW.Me.Inventory.Equipped.OffHand;

                if (weapon != null && weapon.TemporaryEnchantment.Id > 0)
                    retval = weapon.TemporaryEnchantment.Name;
                //debug("Weapon {0} enchantment = '{1}'", slotid, retval);
                return retval;

            }

            public void StartReplace()
            {
                properties = new Dictionary<ulong, Dictionary<string, double>>();
            }

            public double GetProperty(WoWUnit unit, string name)
            {
                // Check the dictionary first
                ulong guid;
                if (unit != null)
                    guid = unit.Guid;
                else
                    guid = 0;
                Dictionary<string, double> values;
                double value = 0;
                if (!properties.ContainsKey(guid))
                {
                    values = new Dictionary<string, double>();
                    properties[guid] = values;
                }
                else
                {
                    values = properties[guid];
                }
                if (!values.ContainsKey(name))
                {
                    value = GetPropValue(unit, name);
                    values[name] = value;
                }
                else
                {
                    value = values[name];
                }
                return value;

            }

            private double GetPropValue(WoWUnit theunit, string name)
            {
                double value = 0;
                switch (name)
                {
                    case "IsHostile": value = theunit != null && theunit.IsHostile ? 1 : 0; break;
                    case "AvgHealth": value = avgHealth; break;
                    case "AddsCount": value = adds.Count; break;
                    case "StunCount": value = theunit != null ? StunCount(theunit.Location) : 0; break;
                    case "Aggro": value = theunit != null ? (int)theunit.ThreatInfo.ThreatStatus : 5; break;
                    case "MovementDisabled": value = theunit != null && MovementDisabled ? 1 : 0; break;
                    case "ClusterSize": value = theunit != null ? theunit.ClusterSize() : 0; break;
                    case "CapsLock": value = CapsLockDown() ? 1 : 0; break;
                    case "BearForm": value = theunit != null && theunit.HasAura("Bear Form") ? 1 : 0; break;
                    case "CatForm": value = theunit != null && theunit.HasAura("Cat Form") ? 1 : 0; break;
                    case "MoonkinForm": value = theunit != null && theunit.HasAura("Moonkin Form") ? 1 : 0; break;
                    case "NormalForm": value = theunit != null && (theunit.HasAura("Cat Form") == false && theunit.HasAura("Bear Form") == false && theunit.HasAura("Moonkin Form") == false) ? 1 : 0; break;
                    case "FlightForm": value = theunit != null && (theunit.HasAura("Flight Form") || theunit.HasAura("Swift Flight Form")) ? 1 : 0; break;
                    case "FeralForm": value = theunit != null && (theunit.HasAura("Bear Form") || theunit.HasAura("Cat Form")) ? 1 : 0; break;
                    case "ComboPoints": value = StyxWoW.Me.ComboPoints; break;
                    case "AllComboPoints": value = StyxWoW.Me.RawComboPoints; break;
                    case "Rage": value = StyxWoW.Me.RageInfo.CurrentI; break;
                    case "Focus": value = StyxWoW.Me.FocusInfo.CurrentI; break;
                    case "Energy": value = StyxWoW.Me.EnergyInfo.CurrentI; break;
                    case "ShadowOrbs": value = StyxWoW.Me.GetPowerInfo(WoWPowerType.ShadowOrbs).CurrentI; break;
                    case "SoulShards": value = StyxWoW.Me.SoulShardsInfo.CurrentI; break;
                    case "Balance": value = StyxWoW.Me.UnitPower(8); break;
                    case "HolyPower": value = StyxWoW.Me.GetPowerInfo(WoWPowerType.HolyPower).CurrentI; break;
                    case "Chi": value = StyxWoW.Me.UnitPower(12); break;
                    case "BurningEmbers": value = StyxWoW.Me.GetPowerInfo(WoWPowerType.BurningEmbers).CurrentI; break;
                    case "DemonicFury": value = StyxWoW.Me.GetPowerInfo(WoWPowerType.DemonicFury).CurrentI; break;
                    case "EclipseDirection": value = _eclipsedirection; break;
                    case "Distance": value = theunit != null ? theunit.Distance2D : 0; break;
                    case "MeleeRange": value = 4.95; break;
                    case "IsTank": value = StyxWoW.Me.IsTank() ? 1 : 0; break;
                    case "IsHealer": value = StyxWoW.Me.IsHealer() ? 1 : 0; break;
                    case "IsDPS": value = StyxWoW.Me.IsDPS() ? 1 : 0; break;
                    case "IsDemon": value = theunit != null && theunit.CreatureType == WoWCreatureType.Demon ? 1 : 0; break;
                    case "IsElemental": value = theunit != null && theunit.CreatureType == WoWCreatureType.Elemental ? 1 : 0; break;
                    case "IsBeast": value = theunit != null && theunit.CreatureType == WoWCreatureType.Beast ? 1 : 0; break;
                    case "IsCritter": value = theunit != null && theunit.CreatureType == WoWCreatureType.Critter ? 1 : 0; break;
                    case "IsDragon": value = theunit != null && theunit.CreatureType == WoWCreatureType.Dragon ? 1 : 0; break;
                    case "IsGasCloud": value = theunit != null && theunit.CreatureType == WoWCreatureType.GasCloud ? 1 : 0; break;
                    case "IsGiant": value = theunit != null && theunit.CreatureType == WoWCreatureType.Giant ? 1 : 0; break;
                    case "IsHumanoid": value = theunit != null && theunit.CreatureType == WoWCreatureType.Humanoid ? 1 : 0; break;
                    case "IsMechanical": value = theunit != null && theunit.CreatureType == WoWCreatureType.Mechanical ? 1 : 0; break;
                    case "IsNonCombatPet": value = theunit != null && theunit.CreatureType == WoWCreatureType.NonCombatPet ? 1 : 0; break;
                    case "IsTotem": value = theunit != null && theunit.CreatureType == WoWCreatureType.Totem ? 1 : 0; break;
                    case "IsUndead": value = theunit != null && theunit.CreatureType == WoWCreatureType.Undead ? 1 : 0; break;
                    case "Health": value = theunit != null ? (int)theunit.HealthPercent : 100; break;
                    case "Mana": value = theunit != null && (theunit.PowerType == WoWPowerType.Mana || theunit.Class == WoWClass.Druid) ? theunit.ManaPercent : 100; break;
                    case "Mounted": value = theunit != null && (theunit.Mounted) ? 1 : 0; break;
                    case "InBattleground": value = Battlegrounds.IsInsideBattleground ? 1 : 0; break;
                    case "InParty": value = StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid ? 1 : 0; break;
                    case "InRaid": value = StyxWoW.Me.GroupInfo.IsInRaid ? 1 : 0; break;
                    case "IsDiseased": value = theunit != null && theunit.IsDiseased() ? 1 : 0; break;
                    case "Dead": value = theunit != null && theunit.IsDead ? 1 : 0; break;
                    case "IsCasting": value = theunit != null && (theunit.IsCasting || theunit.IsChanneling) ? 1 : 0; break;
                    case "IsMoving": value = theunit != null && theunit.IsMoving ? 1 : 0; break;
                    case "IsFlying": value = theunit != null && theunit.IsFlying ? 1 : 0; break;
                    case "LineOfSight": value = theunit != null && theunit.InLineOfSight ? 1 : 0; break;
                    case "Interruptable": value = theunit != null && theunit.CanInterruptCurrentSpellCast ? 1 : 0; break;
                    case "IsElite": value = theunit != null && (theunit.Elite || theunit.Name.Contains("Training Dummy")) ? 1 : 0; break;
                    case "IsBehind": value = theunit != null && theunit.CurrentTarget != null && theunit.CurrentTarget.MeIsSafelyBehind ? 1 : 0; break;
                    case "IsFacingTarget": value = theunit != null && theunit.CurrentTarget != null && theunit.IsSafelyFacing(theunit.CurrentTarget) ? 1 : 0; break;
                    case "IsFleeing": value = theunit != null && theunit.Fleeing ? 1 : 0; break;
                    case "IsIncapacitated": value = theunit != null && (theunit.IsDead || theunit.IsCrowdControlled()) ? 1 : 0; break;
                    case "IsRooted": value = theunit != null && theunit.HasAuraWithMechanic(WoWSpellMechanic.Rooted, WoWSpellMechanic.Shackled) ? 1 : 0; break;
                    case "IsLooting": value = BotPoi.Current.Type == PoiType.Loot ? 1 : 0; break;
                    case "PetIsMissing": value = StyxWoW.Me.PetIsMissing(); break;
                    case "TotemCount": value = theunit != null ? TotemCount() : 0; break;
                    case "RuneCount": value = StyxWoW.Me.DeathRuneCount +
                        StyxWoW.Me.FrostRuneCount +
                        StyxWoW.Me.BloodRuneCount +
                        StyxWoW.Me.UnholyRuneCount; break;
                    case "DeathRune": value = StyxWoW.Me.DeathRuneCount; break;
                    case "FrostRune": value = StyxWoW.Me.FrostRuneCount; break;
                    case "BloodRune": value = StyxWoW.Me.BloodRuneCount; break;
                    case "UnholyRune": value = StyxWoW.Me.UnholyRuneCount; break;
                    case "RunicPower": value = StyxWoW.Me.RunicPowerPercent; break;
                    case "LevelDiff": value = theunit != null ? theunit.Level - StyxWoW.Me.Level : 0; break;
                    case "Level": value = theunit != null ? theunit.Level : 0; break;
                    default: throw new Exception(string.Format("Unknown property {0}!", name));
                }
                return value;
            }

            private string Replace(string prefix, string s, WoWUnit theunit)
            {
                string fnname = "MBC.Replace";
                MyTimer.Start(fnname);
                if (theunit != null && theunit.Guid == StyxWoW.Me.Guid)
                {
                    if (StyxWoW.Me.HasAura("Eclipse (Solar)"))
                        _eclipsedirection = -1;
                    else if (StyxWoW.Me.HasAura("Eclipse (Lunar)"))
                        _eclipsedirection = 1;
                }

                // Replace aura checks with parenthese (Target.HasMyAura("Bad Aura") > 3)
                string findstring = string.Format(@"{0}\.(?<action>.*)\(""(?<aura>.*)""\)", prefix);
                Regex rg = new Regex(findstring);
                Match match = rg.Match(s);
                Dictionary<String, Object> d = new Dictionary<String, Object>();
                while (match.Success)
                {
                    if (!d.ContainsKey(match.Value))
                    {
                        string action = match.Groups["action"].Value;
                        string aura = match.Groups["aura"].Value;
                        int value = 0;
                        if (false) { }
                        else if (action == "Weapon1HasAura") value = (theunit != null && WeaponEnchant(1) == aura) ? 1 : 0;
                        else if (action == "Weapon2HasAura") value = (theunit != null && WeaponEnchant(2) == aura) ? 1 : 0;
                        else if (action == "AuraExpiring") value = (theunit != null) ? theunit.AuraExpiring(aura) : 0;
                        else if (action == "CanCast") value = (theunit != null && SpellManager.CanCast(aura, theunit)) ? 1 : 0;
                        else if (action == "ClearBehindMe") value = (theunit != null && StyxWoW.Me.ClearBehindMe(aura) ? 1 : 0);
                        else if (action == "HasAura") value = (theunit != null && theunit.HasAura(aura)) ? 1 : 0;
                        else if (action == "HasItem") value = StyxWoW.Me.HasItem(aura) ? 1 : 0;
                        else if (action == "HasMyAura") value = (theunit != null && theunit.HasMyAura(aura)) ? 1 : 0;
                        else if (action == "HasSpell") value = (StyxWoW.Me.HasSpell(aura)) ? 1 : 0;
                        else if (action == "HasTotem") value = HasTotem(aura) ? 1 : 0;
                        else if (action == "IsCasting") value = (theunit != null && theunit.CurrentSpellName() == aura) ? 1 : 0;
                        else if (action == "ItemOnCooldown") value = ItemOnCooldown(aura) ? 1 : 0;
                        else if (action == "MyAuraExpiring") value = (theunit != null) ? theunit.MyAuraExpiring(aura) : 0;
                        else if (action == "MyStackCount") value = (theunit != null) ? theunit.MyStackCount(aura) : 0;
                        else if (action == "OnCooldown") value = OnCooldown(aura) ? 1 : 0;
                        else if (action == "PartyWithAura") value = GetPartyWithAura(aura);
                        else if (action == "PartyWithHealth") value = GetPartyWithHealth(aura);
                        else if (action == "StackCount") value = (theunit != null) ? theunit.StackCount(aura) : 0;
                        else if (action == "NumItems") value = (theunit != null) ? NumItems(aura) : 0;
                        else throw new Exception(string.Format("Unknown action {0}!", action));
                        d.Add(match.Value, 0);
                        s = s.Replace(match.Value, value.ToString());
                    }
                    match = match.NextMatch();

                }

                // Replace simple properties
                for (int i = 0; i < FightThisWay.PropertyNames.Count(); i++)
                {
                    string propname = FightThisWay.PropertyNames[i].Trim();
                    if (propname.Length > 0)
                    {
                        string search = prefix + "." + propname;
                        if (s.Contains(search))
                            s = s.Replace(search, GetProperty(theunit, propname).ToString());
                    }
                }

                // Stop the timer
                MyTimer.Stop(fnname);

                return s;
            }

            #endregion

            #region Actions

            private bool ExecuteAction(string action)
            {
                string fnname = "MBC.ExecuteAction";
                string subfn = string.Format("MBC.ExecuteAction '{0}'", action);
                MyTimer.Start(fnname);
                MyTimer.Start(subfn);
                bool retval = false;
                WoWUnit Me = StyxWoW.Me;
                if (action == null)
                    throw new Exception("ExecuteAction - received null action");
                action = action.Replace("\t", "");
                while (action.StartsWith(" "))
                    action = action.Substring(1, action.Length - 1);
                action = action.Replace("  ", " ");
                if (!action.Contains("."))
                    action = "Target.Cast " + action;

                if (action == "Target.Cast Blacklist" && Me.CurrentTarget != null)
                {
                    WoWUnit mob = Me.CurrentTarget;
                    StyxWoW.Me.ClearTarget();
                    log(Color.Violet, "Blackisting {0} for 60 seconds", mob.SafeName());
                    Blacklist.Add(mob, BlacklistFlags.Pull, TimeSpan.FromSeconds(60));
                    BotPoi.Clear();
                    retval = false;
                }
                else
                {
                    // Get first and last part of string
                    string lastpart = string.Empty;
                    string firstpart = string.Empty;

                    int index = action.IndexOf(" ");
                    if (index > -1)
                    {
                        firstpart = action.Substring(0, index);
                        lastpart = action.Substring(index + 1, action.Length - index - 1);
                    }
                    else
                    {
                        firstpart = action;
                        lastpart = string.Empty;
                    }

                    // Get target of action
                    string[] words = firstpart.Split('.');
                    WoWUnit target = null;
                    switch (words[0])
                    {
                        case "Me": target = Me; break;
                        case "Target": target = Me.CurrentTarget; break;
                        case "Tank": target = tank; break;
                        case "Healer": target = healer; break;
                        case "Heal": target = healTarget; break;
                        case "Revive": target = reviveTarget; break;
                        case "Cleanse": target = cleanseTarget; break;
                        case "Pet": target = Me.Pet; break;
                        case "Add": target = add; break;
                        default: target = null; break;

                    }
                    if (target == null)
                    {
                        retval = false;
                    }
                    else
                    {
                        switch (words[1])
                        {

                            // Actions that use targets (Me, Target, Tank, etc from the list above.)
                            case "DebugCast": retval = On_Cast(firstpart, lastpart, target, false, false, true); break;
                            case "Cast": retval = On_Cast(firstpart, lastpart, target, false, false); break;
                            case "DebugCastNow": retval = On_Cast(firstpart, lastpart, target, true, false, true); break;
                            case "CastNow": retval = On_Cast(firstpart, lastpart, target, true, false); break;
                            case "StopCasting": retval = On_StopCasting(lastpart); break;
                            case "DebugCastOnce": retval = On_Cast(firstpart, lastpart, target, true, true, true); break;
                            case "CastOnce": retval = On_Cast(firstpart, lastpart, target, true, true); break;
                            case "DumpAuras": retval = On_DumpAuras(target); break;
                            case "DumpParty": retval = On_DumpParty(); break;
                            case "PetCast": retval = On_PetCast(lastpart, target, false, false); break;
                            case "PetCastNow": retval = On_PetCast(lastpart, target, true, false); break;
                            case "PetCastOnce": retval = On_PetCast(lastpart, target, true, true); break;
                            case "Use": retval = On_Use(lastpart, target); break;

                            // Actions that don't use targets (don't care what the prefix is - can be any from the list above)
                            case "PullMore": retval = On_PullMore(firstpart, lastpart); break;
                            case "ImbueWeapon1": retval = ImbueWeapon(lastpart, "mainhand", 1); break;
                            case "ImbueWeapon2": retval = ImbueWeapon(lastpart, "offhand", 2); break;
                            case "CastAll": retval = On_CastAll(firstpart, lastpart); break;
                            case "InterruptAny": retval = On_Interrupt(firstpart, lastpart); break;
                            case "ExecuteAny": retval = On_Execute(firstpart, lastpart); break;
                            case "ParalyzeAny": retval = On_Paralyze(firstpart, lastpart); break;
                            case "Message": retval = false; log(lastpart); break;
                            case "Range": retval = false; range = double.Parse(lastpart); log("Combat range set to {0:0.00}", range); break;
                            case "ClipTime": retval = false; cliptime = Int32.Parse(lastpart); log("Spells will be clipped with {0} ms remaining cast time.", cliptime); break;
                            case "Eat": retval = On_Eat(); break;
                            case "Drink": retval = On_Drink(); break;
                            case "UseBandage": retval = On_UseBandage(); break;
                            case "UseHealthPotion": retval = false;  On_UseHealthPotion(); break;
                            case "UseManaPotion": retval = false;  On_UseManaPotion(); break;
                            case "FollowTarget": retval = FollowTarget(); break;
                            case "FindBetterTarget": retval = On_FindBetterTarget("by script request"); break;
                            case "PetAttack": retval = On_PetAttack(); break;
                            case "AutoAttack": retval = On_AutoAttack(); break;
                            case "OnCooldown": retval = OnCooldown(lastpart); break;
                            case "Macro": retval = false; Macro(lastpart); break;
                            case "LootChestsOn": CharacterSettings.Instance.LootChests = true; break;
                            case "LootChestsOff": CharacterSettings.Instance.LootChests = false; break;
                            case "GrabAggro": retval = On_GrabAggro(); break;
                            case "LeaveEarly": retval = true; break;

                            // Unknown action
                            default: retval = On_UnimplementedAction(action); break;
                        }
                    }

                }

                MyTimer.Stop(subfn);
                MyTimer.Stop(fnname);
                return retval;

            }

            private bool On_PetAttack()
            {
                bool retval;
                string fnname = "MBC.On_PetAttack";
                MyTimer.Start(fnname);
                using (StyxWoW.Memory.AcquireFrame())
                {
                    if (!(StyxWoW.Me.GotAlivePet))
                    {
                        log(Color.Red, "Don't have alive pet");
                        retval = false;
                    }
                    else
                    {
                        WoWUnit pet = StyxWoW.Me.Pet;
                        if (pet.CurrentTargetGuid == StyxWoW.Me.CurrentTargetGuid)
                        {
                            retval = false;
                        }
                        else
                        {
                            log("    Siccing pet on {0} health {1:0} dist {2:0.0}", StyxWoW.Me.CurrentTarget.SafeName(),
                                StyxWoW.Me.CurrentTarget.HealthPercent, StyxWoW.Me.CurrentTarget.DistanceCalc());
                            foreach (WoWPetSpell sp in StyxWoW.Me.PetSpells)
                            {
                                if (sp.Action.ToString() == "Attack")
                                {
                                    Lua.DoString("CastPetAction({0})", sp.ActionBarIndex + 1);
                                    break;
                                }
                            }
                            retval = false;
                        }
                    }
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_Eat()
            {
                string fnname = "MBC.On_Eat";
                MyTimer.Start(fnname);
                bool retval;
                if (BotPoi.Current.Type != PoiType.None)
                    return false;

                if (StyxWoW.Me.HasAura("Food"))
                {
                    retval = true;
                }
                else if (OnCooldown("On_Eat"))
                {
                    retval = false;
                }
                else if (MovementDisabled)
                {
                    retval = false;
                }
                else if (StyxWoW.Me.Mounted)
                {
                    retval = false;
                }
                else
                {
                    WoWItem eat = Consumable.GetBestFood(false);
                    if (eat == null)
                    {
                        retval = false;
                    }
                    else
                    {
                        Navigator.PlayerMover.MoveStop();
                        Styx.CommonBot.Rest.FeedImmediate();
                        DateTime stoptime = DateTime.Now.AddSeconds(10);
                        while (DateTime.Now < stoptime)
                        {
                            if (StyxWoW.Me.HasAura("Food"))
                                break;
                            Thread.Sleep(100);

                        }
                        SaveCooldown("On_Eat");
                        retval = true;
                    }
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_Drink()
            {
                bool retval;
                if (BotPoi.Current.Type != PoiType.None)
                    return false;
                string fnname = "MBC.On_Drink";
                MyTimer.Start(fnname);
                if (StyxWoW.Me.HasAura("Drink"))
                {
                    retval = true;
                }
                else if (OnCooldown("On_Drink"))
                {
                    retval = false;
                }
                else if (MovementDisabled)
                {
                    retval = false;
                }
                else if (StyxWoW.Me.Mounted)
                {
                    retval = false;
                }
                else
                {
                    WoWItem drink = Consumable.GetBestDrink(false);
                    if (drink == null)
                    {
                        retval = false;
                    }
                    else
                    {
                        Navigator.PlayerMover.MoveStop();
                        Styx.CommonBot.Rest.DrinkImmediate();
                        DateTime stoptime = DateTime.Now.AddSeconds(10);
                        while (DateTime.Now < stoptime)
                        {
                            if (StyxWoW.Me.HasAura("Drink"))
                                break;
                            Thread.Sleep(100);

                        }
                        SaveCooldown("On_Drink");
                        retval = true;
                    }
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_UseHealthPotion()
            {
                return On_UsePotion(healthspellnames);
            }

            private bool On_UseManaPotion()
            {
                return On_UsePotion(manaspellnames);
            }

            private bool On_UsePotion(HashSet<String> spellnames)
            {
                bool retval = true;
                string fnname = "MBC.On_UsePotion";
                MyTimer.Start(fnname);
                using (StyxWoW.Memory.AcquireFrame())
                {
                    if (!FightThisWay.ValidState())
                    {
                        retval = false;
                    }
                    else
                    {
                        WoWItem potion = (from i in StyxWoW.Me.CarriedItems
                                          let spells = i.ItemSpells
                                          where i.ItemInfo != null && spells != null && spells.Count != 0 &&
                                                i.Usable && i.Cooldown == 0 && i.ItemInfo.RequiredLevel <= StyxWoW.Me.Level &&
                                                spells.Any(s => s.IsValid && s.ActualSpell != null && spellnames.Contains(s.ActualSpell.Name))
                                          orderby i.ItemInfo.Level descending
                                          select i).FirstOrDefault();
                        if (debugitems.Count > 0)
                        {
                            foreach (WoWItem item in StyxWoW.Me.CarriedItems)
                            {
                                if (debugitems.Contains(item.Name))
                                {
                                    List<WoWItem.WoWItemSpell> spells = item.ItemSpells;
                                    ItemInfo inf = item.ItemInfo;
                                    debug(string.Format("{0} {1} {2}", item.Name, item.Usable, item.Cooldown));
                                    if (spells != null)
                                    {
                                        foreach (WoWItem.WoWItemSpell itemspell in spells)
                                        {
                                            debug(string.Format("    {0} {1}", itemspell.Id, itemspell.ToString()));
                                            WoWSpell actualspell = itemspell.ActualSpell;
                                            if (actualspell != null)
                                            {
                                                debug(string.Format("        {0} {1} {2}", actualspell.Id, actualspell.Name, actualspell.IsValid));
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        if (potion == null)
                        {
                            retval = false;
                        }
                        else
                        {
                            potion.UseContainerItem();
                            log(Color.SkyBlue, "Using {0}", potion.Name);
                            retval = true;
                        }
                    }
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_UseBandage()
            {
                bool retval = true;
                if (!(BotPoi.Current.Type == PoiType.None || BotPoi.Current.Type == PoiType.Kill))
                    return false;

                string fnname = "MBC.On_UseBandge";
                if (StyxWoW.Me.HasAura("Recently Bandaged"))
                    return false;
                MyTimer.Start(fnname);
                using (StyxWoW.Memory.AcquireFrame())
                {
                    if (!FightThisWay.ValidState())
                    {
                        retval = false;
                    }
                    else if (MovementDisabled)
                    {
                        retval = false;
                    }
                    else if (StyxWoW.Me.Mounted)
                    {
                        retval = false;
                    }
                    else
                    {
                        WoWItem potion = (from i in StyxWoW.Me.CarriedItems
                                          let spells = i.ItemSpells
                                          where i.GetItemName().Contains("Bandage")
                                          where i.ItemInfo != null && i.ItemInfo.RequiredLevel <= StyxWoW.Me.Level
                                          orderby i.ItemInfo.Level descending
                                          select i).FirstOrDefault();

                        if (potion == null)
                        {
                            retval = false;
                        }
                        else
                        {
                            potion.UseContainerItem();
                            retval = true;
                        }
                    }
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_Use(string itemname, WoWUnit target)
            {
                string fnname = "MBC.On_Use";
                MyTimer.Start(fnname);
                bool retval = false;
                using (StyxWoW.Memory.AcquireFrame())
                {
                    if (itemname.ToLower() == "trinket1" && StyxWoW.Me.Inventory.Equipped.Trinket1 != null)
                    {
                        WoWItem trinket = StyxWoW.Me.Inventory.Equipped.Trinket1;
                        if (trinket.Cooldown == 0 && trinket.Usable == true && trinket.TriggersSpell == true)
                        {
                            log("Using trinket {0}", StyxWoW.Me.Inventory.Equipped.Trinket1.Name);
                            StyxWoW.Me.Inventory.Equipped.Trinket1.Use();
                        }
                    }
                    else if (itemname.ToLower() == "trinket2" && StyxWoW.Me.Inventory.Equipped.Trinket2 != null)
                    {
                        WoWItem trinket = StyxWoW.Me.Inventory.Equipped.Trinket2;
                        if (trinket.Cooldown == 0 && trinket.Usable == true && trinket.TriggersSpell == true)
                        {
                            log("Using trinket {0}", StyxWoW.Me.Inventory.Equipped.Trinket2.Name);
                            StyxWoW.Me.Inventory.Equipped.Trinket2.Use();
                        }
                    }
                    else
                    {
                        WoWItem item = GetInventoryItem(itemname);
                        if (item == null)
                        {
                            debug("Couldn't find item {0}", itemname);
                        }
                        else if (item.Usable == false)
                        {
                            debug("Item {0} is not usable", item.Name);
                        }
                        else if (item.Cooldown > 0)
                        {
                            debug("Item {0} is on cooldown. Time remaining: {1}", item.Name, item.CooldownTimeLeft.TotalSeconds);
                        }
                        else if (OnCooldown(item.Name))
                        {
                            debug("Item {0} is on cooldown.", item.Name);
                        }
                        else
                        {
                            if (target != null && target.Guid != StyxWoW.Me.Guid)
                            {
                                target.Target();
                                Thread.Sleep(100);
                            }
                            log("Using item '{0}'", item.Name);
                            item.Use();
                            retval = false;
                        }
                    }
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_StopCasting(string spellname)
            {

                string currentspellname = string.Empty;
                string currentchannelname = string.Empty;
                if (StyxWoW.Me.CastingSpell != null)
                    currentspellname = StyxWoW.Me.CastingSpell.Name;
                if (StyxWoW.Me.ChanneledSpell != null)
                    currentspellname = StyxWoW.Me.ChanneledSpell.Name;
                if (spellname == currentspellname || spellname == currentchannelname)
                {
                    SpellManager.StopCasting();
                    return true;
                }
                return false;
            }

            private bool On_Cast(string firstpart, string spellname, WoWUnit target, bool castnow, bool castonce, bool debugspell = false)
            {
                string fnname = "MBC.On_Cast";
                MyTimer.Start(fnname);
                LocalPlayer Me = StyxWoW.Me;
                WoWSpell spell = null;
                bool retval = true;
                Color ds = Color.Magenta;

                if (target != null && !target.IsFriendly && target.IsDead)
                {
                    if (debugspell) log(ds, "Target is hostile and dead");
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (OnCooldown(spellname, debugspell))
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (target != null && !target.IsFriendly && !MovementDisabled)
                    Face(target);

                // Clear out expired CastOnce entries (older than CastOnceTime seconds)
                foreach (KeyValuePair<String, DateTime> kvp in CastOnce.ToList())
                {
                    if (DateTime.Now > kvp.Value)
                        CastOnce.Remove(kvp.Key);
                }

                // Leave early if CastOnce contains unexpired entry for this unit
                if (CastOnce.ContainsKey(spellname + target.Guid.ToString()))
                {
                    if (debugspell) log(ds, "Already cast {0} on {1}", spellname, target.Name);
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (SpellManager.GlobalCooldown)
                {
                    if (debugspell) log(ds, "...");
                    MyTimer.Stop(fnname);
                    return false;
                }

                try
                {
                    if (fakecooldowns.ContainsKey(spellname))
                        spell = WoWSpell.FromId(fakecooldowns[spellname].SpellID);
                    else if (SpellManager.Spells.ContainsKey(spellname))
                        spell = SpellManager.Spells[spellname];
                    else
                    {
                        debug(Color.Gray, "Unknown spell {0}", spellname);
                        MyTimer.Stop(fnname);
                        return false;
                    }
                }
                catch (Exception ex)
                {
                    log(Color.Pink, "Error when getting spell {0}: {1}", spellname, ex.Message);
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (spell == null)
                {
                    debug(Color.Gray, "Unknown spell '{0}'", spellname);
                    MyTimer.Stop(fnname);
                    return false;
                }

                // Check whether or not you know the spell according to WoW
                if (false) // leave commented out for now
                {
                    List<string> known = Lua.GetReturnValues(String.Format("return IsSpellKnown({0})", spell.Id), "SpellKnown.lua");
                    int knownspell = Int32.Parse(known[0]);
                    if (knownspell == 0)
                    {
                        log(Color.Gray, "GetSpellInfo says you don't know spell {0} ({1})", spellname, known[0]);
                        MyTimer.Stop(fnname);
                        return false;
                    }
                }

                if (false)
                {
                    // Check power requirements
                    List<string> values = Lua.GetReturnValues(String.Format("return GetSpellInfo({0})", spell.Id), "SpellInfo.lua");
                    //string[] vars = {"name", "rank", "icon", "cost", "isFunnel", "powerType", "castTime", "minRange", "maxRange"};
                    int powerType = Int32.Parse(values[5]);
                    int powerCost = Int32.Parse(values[3]);
                    string[] powertypes = { "mana", "rage", "focus", "energy", 
                                        "happiness", "runes", "runic power", "soul shards", 
                                        "eclipse", "holy power", "alternate power", "dark force", 
                                        "chi", "shadow orbs", "burning embers", "demonic fury"
                                      };
                    if (StyxWoW.Me.UnitPower(powerType) < powerCost)
                    {
                        log(Color.Orange, "NOT ENOUGH {0} - Requires: {1}, but I have: {2}", powertypes[powerType], powerCost, StyxWoW.Me.UnitPower(powerType));
                        MyTimer.Stop(fnname);
                        return false;
                    }
                }

                if (!spell.IsSelfOnlySpell)
                {
                    if (target == null)
                    {
                        log(Color.Orange, "Can't cast {0} on null target!", spell.Name);
                        if (debugspell) log(ds, "Null target");
                        MyTimer.Stop(fnname);
                        return false;
                    }
                    double dist = target.Distance; // target.DistanceCalc();
                    if ((spell.MinRange > 0 || spell.MaxRange > 0) && !(dist >= spell.MinRange && dist <= spell.MaxRange))
                    {
                        log(Color.Orange, "Can't cast spell {0} {1} on {2} health {3:0.0} dist {4:0.0} minrange {5} maxrange {6}", spellname, spell.Id, target.SafeName(), target.HealthPercent, target.DistanceCalc(), spell.MinRange, spell.MaxRange);
                        if (debugspell) log(ds, "Out of range");
                        MyTimer.Stop(fnname);
                        return false;
                    }
                    if (!target.InLineOfSpellSight)
                    {
                        log(Color.Orange, "Can't cast spell {0} {1} on {2} health {3:0.0} dist {4:0.0} (not in line of sight)", spellname, spell.Id, target.SafeName(), target.HealthPercent, target.DistanceCalc());
                        if (debugspell) log(ds, "Not in line of sight.");
                        MyTimer.Stop(fnname);
                        return false;
                    }

                    if (spellname == "Death Pact" && Me.GotAlivePet)
                    {
                        Me.Pet.Target();
                        for (int l = 0; l < 2; l++)
                            Thread.Sleep(10);
                    }

                    if (CastOver.Contains(lastspellcastname))
                        retval = true;
                    else if (spell.IsSelfOnlySpell)
                        retval = SpellManager.CanCast(spell, true);
                    else
                        retval = SpellManager.CanCast(spell, target, true);

                    if (FightThisWay.IgnoreCanCast.Contains(spellname))
                    {
                        if (debugspell) log(ds, "Ignoring cancast for {0}", spellname);
                        retval = true;
                    }
                    if (retval == false)
                    {
                        if (spell.IsSelfOnlySpell)
                            debug(Color.Orange, "CanCast returned false for {0} {1} on me", spellname, spell.Id);
                        else
                            debug(Color.Orange, "CanCast returned false for {0} {1} on {2} health {3:0.0} dist {4:0.0}", spellname, spell.Id, target.SafeName(), target.HealthPercent, target.DistanceCalc());
                        MyTimer.Stop(fnname);
                        if (debugspell) log(ds, "CanCast returned false");
                        return false;
                    }
                }

                // Leave early if still casting
                if (!castnow && (Me.IsCasting || Me.IsChanneling))
                {
                    if (debugspell) log(ds, "Still casting");
                    return false;
                }
                // Definitely going to cast - Stop casting
                if (Me.IsCasting || Me.IsChanneling)
                {
                    if (CastOver.Contains(lastspellcastname))
                        log("Currently casting {0} - casting over it with {1}", lastspellcastname, spellname);
                    else if (castnow)
                        SpellManager.StopCasting();
                }

                // Stop moving
                if (spell.CastTime > 0 && !MovementDisabled)
                    WoWMovement.MoveStop();

                // Clear target spell, if one is up
                if (StyxWoW.Me.CurrentPendingCursorSpell != null)
                    Lua.DoString("SpellStopTargeting()");

                if (spell.IsTankOnlySpell() == true)
                {
                    // Spells that are only ever cast on tanks, such as Misdirection and Tricks of the Trade.
                    if (StyxWoW.Me.GotAlivePet && Me.Pet.IsDead == false)
                    {
                        log("cast tank-only spell {0} on pet", spell.Name);
                        if (SpellManager.CanCast(spell, Me.Pet))
                        {
                            log("nope - failed cancast check.");
                            retval = SpellManager.Cast(spell, Me.Pet);
                        }
                    }
                    else if (people.Count > 1 && tank.Guid != Me.Guid && tank.IsDead == false)
                    {
                        log("cast tank-only spell {0} on {1}", spell.Name, tank.SafeName());
                        if (SpellManager.CanCast(spell, tank))
                        {
                            log("nope - failed cancast check.");
                            retval = SpellManager.Cast(spell, tank);
                        }
                    }
                    else
                        retval = false;
                }
                else if (spell.IsAreaSpell() == false)
                {
                    // Most normal spells
                    if (spell.IsSelfOnlySpell)
                        retval = SpellManager.Cast(spell);
                    else
                        retval = SpellManager.Cast(spell, target);

                    // Post-spell processing
                    if (spellname == "Feign Death")
                    {
                        while (Me.HasAura("Feign Death"))
                        {
                            List<WoWUnit> mobs = (List<WoWUnit>)GetNearbyUnfriendlyUnits();
                            if (mobs.Count == 0 && StyxWoW.Me.HealthPercent > 95)
                                break;
                            Thread.Sleep(10);
                        }
                    }
                }
                else
                {
                    // Area spell
                    WoWPoint loc;

                    if (spell.IsSelfOnlySpell && !spell.Name.EndsWith(" Trap"))
                        loc = StyxWoW.Me.Location;
                    else
                        loc = target.Location;
                    retval = SpellManager.Cast(spell);
                    DateTime stoptime = DateTime.Now.AddMilliseconds(500);
                    while (DateTime.Now < stoptime)
                    {
                        if (StyxWoW.Me.CurrentPendingCursorSpell != null)
                            break;
                        Thread.Sleep(10);
                    }
                    SpellManager.ClickRemoteLocation(loc);
                    stoptime = DateTime.Now.AddMilliseconds(500);
                    while (DateTime.Now < stoptime)
                    {
                        // Clear target spell, if one is up
                        if (StyxWoW.Me.CurrentPendingCursorSpell == null)
                            break;
                        Thread.Sleep(10);
                    }
                    // Clear target spell, if one is up
                    if (StyxWoW.Me.CurrentPendingCursorSpell != null)
                        Lua.DoString("SpellStopTargeting()");

                }

                // Add current spell to CastOnce dictionary
                if (castonce == true)
                    CastOnce[spellname + target.Guid.ToString()] = DateTime.Now.AddSeconds(CastOnceTime);

                // Wait until spell starts casting
                if (spell.CastTime > 0)
                {
                    DateTime dt = DateTime.Now.AddSeconds(1.5);
                    while (DateTime.Now < dt && !StyxWoW.Me.IsCasting && !StyxWoW.Me.IsChanneling)
                        Thread.Sleep(10);
                }

                // Save spell cooldown
                if (retval == true)
                {
                    log("    {0} {1} on {2} {3} health {4:0} dist {5:0.0} aggro {6} {7} (addscount {8})", firstpart, spellname, target.SafeName(), target.ShortGuid(), target.HealthPercent, target.DistanceCalc(), (int)target.ThreatInfo.ThreatStatus, target.ThreatInfo.ThreatStatus, adds.Count);
                    SaveCooldown(spellname);
                    MovementCooldown = DateTime.Now.Add(TimeSpan.FromMilliseconds(Math.Min(1500, spell.CastTime)));
                }
                else
                {
                    log(Color.Gray, "  - Couldn't cast spell {0} on {1} {2} at dist {3:0.0}", spellname, target.SafeName(), target.ShortGuid(), target.DistanceCalc());
                }

                if (Stuns.Contains(spellname))
                {
                    // Don't attack again for 3 seconds - hb is slow in noticing cc's.
                    Blacklist.Add(target.Guid, BlacklistFlags.Combat, TimeSpan.FromSeconds(3));
                    if (target.Guid == StyxWoW.Me.CurrentTargetGuid)
                        StyxWoW.Me.ClearTarget();
                }

                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_PetCast(string spellname, WoWUnit target, bool castnow, bool castonce)
            {
                string fnname = "MBC.On_PetCast";
                MyTimer.Start(fnname);
                LocalPlayer Me = StyxWoW.Me;
                WoWPetSpell spell = null;

                // Don't even try to cast if I don't have a pet.
                if (!Me.GotAlivePet)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                // Clear out expired CastOnce entries (older than CastOnceTime seconds)
                foreach (KeyValuePair<String, DateTime> kvp in CastOnce.ToList())
                {
                    if (DateTime.Now > kvp.Value)
                        CastOnce.Remove(kvp.Key);
                }

                // Leave early if CastOnce contains unexpired entry for this unit
                if (CastOnce.ContainsKey(spellname + target.Guid.ToString()))
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                foreach (WoWPetSpell sp in StyxWoW.Me.PetSpells)
                {
                    if (sp.Action.ToString() == spellname)
                    {
                        Lua.DoString("CastPetAction({0})", sp.ActionBarIndex + 1);
                        log("[Pet] Action = {0}", sp.Action.ToString());
                        break;
                    }

                    else if (sp.Spell != null && sp.Spell.Name == spellname)
                    {
                        spell = sp;
                        break;
                    }
                }
                if (spell == null)
                {
                    debug(Color.Gray, "[Pet] Unknown spell {0}", spellname);
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (OnPetCooldown(spellname))
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (!castnow && (Me.Pet.IsCasting || Me.Pet.IsChanneling))
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (spell.Spell.MinRange > 0 && target.DistanceCalc() < spell.Spell.MinRange)
                {
                    debug(Color.Yellow, "[Pet] Too close to {0} to cast {1}.", target.SafeName(), spell.Spell.Name);
                    MyTimer.Stop(fnname);
                    return false;
                }
                if (spell.Spell.MaxRange > 0 && target.DistanceCalc() > spell.Spell.MaxRange)
                {
                    debug(Color.Yellow, "[Pet] Too far away to {0} to cast {1}.", target.SafeName(), spell.Spell.Name);
                    MyTimer.Stop(fnname);
                    return false;
                }

                string strTarget;
                if (target.Guid == StyxWoW.Me.Guid)
                    strTarget = "player";
                else if (target.Guid == StyxWoW.Me.Pet.Guid)
                    strTarget = "pet";
                else if (target.Guid == StyxWoW.Me.CurrentTargetGuid)
                    strTarget = "target";
                else
                    strTarget = null;

                // Add current spell to CastOnce dictionary
                if (castonce == true)
                    CastOnce[spellname + target.Guid.ToString()] = DateTime.Now.AddSeconds(CastOnceTime);

                if (strTarget == null)
                {
                    Lua.DoString("CastPetAction({0})", spell.ActionBarIndex + 1);
                    log("    [Pet] Cast {0}", spell.Spell.Name);
                }
                else
                {
                    Lua.DoString("CastPetAction({0}, {1})", spell.ActionBarIndex + 1, strTarget);
                    log("    [Pet] Cast {0} on {1} health {2:0.0} dist {3:0.0}", spell.Spell.Name, strTarget, target.HealthPercent, target.DistanceCalc());
                }

                if (spell.Spell.IsAreaSpell())
                {
                    // Area spell
                    WoWPoint loc;

                    if (spell.Spell.IsSelfOnlySpell)
                        loc = StyxWoW.Me.Location;
                    else
                        loc = target.Location;

                    DateTime stoptime = DateTime.Now.AddMilliseconds(500);
                    while (DateTime.Now < stoptime)
                    {
                        if (StyxWoW.Me.CurrentPendingCursorSpell != null)
                            break;
                        Thread.Sleep(10);
                    }
                    SpellManager.ClickRemoteLocation(loc);
                    stoptime = DateTime.Now.AddMilliseconds(500);
                    while (DateTime.Now < stoptime)
                    {
                        // Clear target spell, if one is up
                        if (StyxWoW.Me.CurrentPendingCursorSpell == null)
                            break;
                        Thread.Sleep(10);
                    }
                    // Clear target spell, if one is up
                    if (StyxWoW.Me.CurrentPendingCursorSpell != null)
                        Lua.DoString("SpellStopTargeting()");
                }

                SaveCooldown(spell.Spell.Name);

                MyTimer.Stop(fnname);

                // We have no way of knowing if this spell was successfully cast or not.
                // Therefore, return false so it doesn't stop the action sequence.
                return false;

            }

            private bool On_Interrupt(string firstpart, string spellname)
            {
                // Interrupts the spellcaster furthest away from you
                string fnname = "MBC.On_Interrupt";
                MyTimer.Start(fnname);
                int range = 5;
                WoWSpell spell = null;
                try
                {
                    if (fakecooldowns.ContainsKey(spellname))
                        spell = WoWSpell.FromId(fakecooldowns[spellname].SpellID);
                    else
                        spell = SpellManager.Spells[spellname];
                }
                catch (KeyNotFoundException ex) { }
                if (spell == null || spell.CooldownTimeLeft.TotalMilliseconds > 0)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }
                range = Math.Max(range, (int)spell.MaxRange);
                WoWUnit castingmob = (from a in adds
                                      where a.HealthPercent > 30
                                      where a.InLineOfSight
                                      where (a.IsCasting == true || a.IsChanneling == true)
                                      where a.Distance < range
                                      where a.CanInterruptCurrentSpellCast == true
                                      orderby a.Distance descending
                                      select a).FirstOrDefault();
                if (castingmob == null)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }
                if (MovementDisabled)
                    log(Color.Violet, "MOVEMENT DISABLED - Not facing target on interrupt!");
                else
                    Face(castingmob);
                bool retval = On_Cast(firstpart, spellname, castingmob, true, false);
                if (retval)
                {
                    string effect1 = "";
                    try
                    {
                        effect1 = WoWSpell.FromId((int)castingmob.CurrentCastId).SpellEffect1.ToString();
                    }
                    catch (Exception ex)
                    {
                    }
                    log(Color.GreenYellow, "    Interrupted {0} with {1} dist {2:0.0} effect {3}", castingmob.SafeName(), spellname, castingmob.Distance, effect1);
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_Execute(string firstpart, string spellname)
            {
                // Interrupts the spellcaster furthest away from you
                string fnname = "MBC.On_Execute";
                MyTimer.Start(fnname);
                int range = 5;
                WoWSpell spell = null;
                try
                {
                    if (fakecooldowns.ContainsKey(spellname))
                        spell = WoWSpell.FromId(fakecooldowns[spellname].SpellID);
                    else
                        spell = SpellManager.Spells[spellname];
                }
                catch (KeyNotFoundException ex) { }
                if (spell == null || spell.CooldownTimeLeft.TotalMilliseconds > 0)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }
                range = Math.Max(range, (int)spell.MaxRange);
                WoWUnit mob = (from a in adds
                               where a.HealthPercent < 25
                               where a.InLineOfSight
                               //where (a.IsCasting == true || a.IsChanneling == true)
                               where a.Distance < range
                               //where a.CanInterruptCurrentSpellCast == true
                               orderby a.Distance descending
                               select a).FirstOrDefault();
                if (mob == null)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }
                if (MovementDisabled)
                    log(Color.Violet, "MOVEMENT DISABLED - Not facing target on execute!");
                else
                    Face(mob);
                bool retval = On_Cast(firstpart, spellname, mob, true, false);
                if (retval)
                {

                    log(Color.GreenYellow, "    Executed on {0} with {1} dist {2:0.0} ", mob.SafeName(), spellname, mob.Distance);
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_PullMore(string firstpart, string spellname)
            {
                // Pulls another mob with the specified spell.
                if (!(BotPoi.Current.Type == PoiType.Kill || BotPoi.Current.Type == PoiType.None))
                    return false;
                if (MovementDisabled)
                    return false;
                if (StyxWoW.Me.HealthPercent < 80)
                    return false;
                int range = 5;
                WoWSpell spell = null;
                try
                {
                    if (fakecooldowns.ContainsKey(spellname))
                        spell = WoWSpell.FromId(fakecooldowns[spellname].SpellID);
                    else
                        spell = SpellManager.Spells[spellname];
                }
                catch (KeyNotFoundException ex) { }
                if (spell == null || spell.CooldownTimeLeft.TotalMilliseconds > 0)
                    return false;
                range = Math.Max(range, (int)spell.MaxRange);
                WoWUnit anothermob = (from u in ObjectManager.GetObjectsOfType<WoWUnit>(false, false)
                                      where u.IsValidUnit()
                                      where !u.TaggedByOther
                                      where !u.IsDead
                                      where !u.TaggedByOther
                                      where u.IsHostile
                                      where u.DistanceCalc() <= 40
                                      where !u.Mounted
                                      where u.InLineOfSight
                                      where u.Distance < range
                                      orderby u.Distance descending
                                      select u).FirstOrDefault();
                if (anothermob == null)
                    return false;
                if (MovementDisabled)
                    log(Color.Violet, "MOVEMENT DISABLED - Not facing target on pullmore!");
                else
                    Face(anothermob);
                bool retval = On_Cast(firstpart, spellname, anothermob, true, false);
                if (retval)
                    log(Color.GreenYellow, "    Taunted {0} {1} dist {2:0.0} with {3} (addscount {4})", anothermob.SafeName(), anothermob.ShortGuid(), anothermob.Distance, spellname, adds.Count);
                return retval;
            }

            private bool On_Paralyze(string firstpart, string spellname)
            {
                // Paralyzes the spellcaster furthest away from you
                // Similar to On_Interrupt, except doesn't check for interruptability
                // and prioritizes mobs attacking the healer over others.
                int range = 5;
                WoWSpell spell = null;
                try
                {
                    if (fakecooldowns.ContainsKey(spellname))
                        spell = WoWSpell.FromId(fakecooldowns[spellname].SpellID);
                    else
                        spell = SpellManager.Spells[spellname];
                }
                catch (KeyNotFoundException ex) { }
                if (spell == null || spell.CooldownTimeLeft.TotalMilliseconds > 0)
                    return false;
                range = Math.Max(range, (int)spell.MaxRange);
                WoWUnit castingmob = (from a in adds
                                      where a.HealthPercent > 30
                                      where a.InLineOfSight
                                      where a.IsCasting == true ||
                                      a.IsChanneling == true ||
                                      a.IsAttackingHealer() == true
                                      where a.Distance < range
                                      orderby a.IsAttackingHealer() descending, a.Distance descending
                                      select a).FirstOrDefault();
                if (castingmob == null)
                    return false;
                if (MovementDisabled)
                    log(Color.Violet, "MOVEMENT DISABLED - Not facing target on paralyze!");
                else
                    Face(castingmob);

                bool retval = On_Cast(firstpart, spellname, castingmob, true, false);
                if (retval)
                    log(Color.GreenYellow, "    Paralyzed {0} with {1} dist {2:0.0} attackinghealer {3}", castingmob.SafeName(), spellname, castingmob.Distance, castingmob.IsAttackingHealer());
                return retval;
            }

            private bool On_CastAll(string firstpart, string auraname)
            {
                bool retval = false;
                string fnname = "MBC.On_CastAll";
                MyTimer.Start(fnname);
                WoWUnit mobwithoutaura = (from a in adds
                                          where a.HealthPercent > 30
                                          where a.InLineOfSight
                                          where a.Distance < 40
                                          where a.Elite == true
                                          where a.HasMyAura(auraname) == false
                                          select a).FirstOrDefault();

                if (mobwithoutaura != null)
                {
                    if (MovementDisabled)
                        log(Color.Violet, "MOVEMENT DISABLED - Not facing target in CastAll");
                    else
                        Face(mobwithoutaura);
                    retval = On_Cast(firstpart, auraname, mobwithoutaura, false, false);
                }

                MyTimer.Stop(fnname);
                return retval;
            }

            private void Face(WoWUnit mob)
            {
                DateTime stoptime = DateTime.Now.AddSeconds(1);
                if (StyxWoW.Me.Location == mob.Location)
                {
                    KeyboardManager.PressKey((char)Keys.Back);
                    Thread.Sleep(50);
                    KeyboardManager.ReleaseKey((char)Keys.Back);
                    return;
                }
                while (!StyxWoW.Me.IsSafelyFacing(mob) && DateTime.Now < stoptime)
                {
                    debug(Color.Violet, "Facing {0}", mob.SafeName());
                    mob.Face();
                    Thread.Sleep(10);
                }
            }
            private bool On_AutoAttack()
            {
                string fnname = "MBC.On_AutoAttack";
                MyTimer.Start(fnname);
                if (!StyxWoW.Me.IsAutoAttacking && StyxWoW.Me.CurrentTarget != null)
                {
                    log("    AutoAttack");
                    Lua.DoString("StartAttack()");
                }
                MyTimer.Stop(fnname);
                return false;
            }

            public bool On_FindBetterTarget(string reason)
            {
                string fnname = "MBC.On_FindBetterTarget";
                MyTimer.Start(fnname);
                if (MovementDisabled)
                {
                    log(Color.Violet, "MOVEMENT DISABLED, not looking for better target");
                    MyTimer.Stop(fnname);
                    return false;
                }
                WoWUnit target = StyxWoW.Me.CurrentTarget;
                if (target != null && target.IsValidUnit() && !target.IsDead && !target.IsCrowdControlled() && OnCooldown("FindBetterTarget"))
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                bool retval = ListAndTakeNewTarget(adds);
                if (retval == true)
                    SaveCooldown("FindBetterTarget");
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool On_GrabAggro()
            {
                string fnname = "MBC.On_GrabAggro";
                MyTimer.Start(fnname);
                if (MovementDisabled)
                {
                    log(Color.Violet, "MOVEMENT DISABLED - Not grabbing aggro.");
                    MyTimer.Stop(fnname);
                    return false;
                }
                WoWUnit target = StyxWoW.Me.CurrentTarget;
                if (target != null && target.IsValidUnit() && OnCooldown("GrabAggro"))
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                List<WoWUnit> units =
                    (from u in adds
                     where u.Combat && u.IsValidUnit()
                     orderby
                             u.TargetWeight() descending,
                             u.IsCrowdControlled() ascending,
                             u.IsAttackingHealer() descending,
                             (int)u.ThreatInfo.ThreatStatus < 4 descending,
                             u.Guid == StyxWoW.Me.CurrentTargetGuid descending,
                             u.Guid == tank.CurrentTargetGuid descending,
                             u.Distance < 5 descending,
                             u.DistanceSqr ascending
                     select u).ToList();

                bool retval = ListAndTakeNewTarget(units);
                if (retval == true)
                    SaveCooldown("GrabAggro");
                MyTimer.Stop(fnname);
                return retval;

            }

            private bool On_UnimplementedAction(string action)
            {
                log("Unimplemented action '{0}'", action);
                return false;
            }

            #endregion

            #region Supporting routines

            public bool IsTargetingPartyPets(WoWUnit unit)
            {

                if (unit == null || !unit.Combat || unit.CurrentTarget == null)
                    return false;
                WoWUnit target = unit.CurrentTarget;
                if (target.OwnedByUnit != null && people.Contains(target.OwnedByUnit) && !unit.IsFriendly)
                {
                    return true;
                }
                return false;
            }

            public WoWPlayer GetFocusedPlayer()
            {
                if (StyxWoW.Me.FocusedUnitGuid != 0)
                {
                    WoWPlayer player = ObjectManager.GetObjectByGuid<WoWPlayer>(StyxWoW.Me.FocusedUnitGuid);
                    if (player != null && people.Contains(player))
                        return player;
                }
                return null;
            }

            private bool CapsLockDown()
            {
                return System.Windows.Forms.Control.IsKeyLocked(System.Windows.Forms.Keys.CapsLock);
            }

            public bool MovementDisabled
            {
                get
                {
                    if (FightThisWay.UseScrollLock == false)
                        return false;
                    if (ScrollLockDown())
                        return true;
                    return false;
                }
            }

            public bool FollowTarget()
            {
                string fnname = "MBC.FollowTarget";
                MyTimer.Start(fnname);
                LocalPlayer Me = StyxWoW.Me;
                WoWUnit followtarget = null;
                double maxDist = range;
                bool retval = false;
                bool followtank = false;
                if (Styx.CommonBot.BotManager.Current.Name == "Grind Bot" &&
                    tank.IsDead == false &&
                    tank.Guid != StyxWoW.Me.Guid)
                    followtank = true;
                else
                    followtank = false;

                if (Me.HasAura("Dark Apotheosis"))
                    maxDist = 10;

                // Leave early if they don't want movement and facing.
                if (MovementDisabled)
                {
                    log(Color.Violet, "Movement disabled, not following");
                }
                else if (DateTime.Now < MovementCooldown || StyxWoW.Me.IsCasting || StyxWoW.Me.IsChanneling)
                {
                    //log(Color.Violet, "Casting, not following");
                }
                else if (BotPoi.Current.Type == PoiType.Loot)
                {
                    log(Color.Violet, "Looting, not following");
                }
                else if (mode == "@PULL")
                {
                    // Just follow whoever is targeted
                    followtarget = StyxWoW.Me.CurrentTarget;
                }
                else if (mode == "@COMBAT")
                {
                    // In combat, follow target preferentially
                    followtarget = StyxWoW.Me.CurrentTarget;
                    if (StyxWoW.Me.IsHealer() && followtank)
                    {
                        followtarget = tank;
                        maxDist = 20;
                    }
                    else if (
                        (followtarget == null ||
                        !followtarget.IsValidUnit()
                    ) && followtank)
                    {
                        followtarget = tank;
                        maxDist = 10;
                    }
                }
                else if (mode == "@REST")
                {
                    // Out of combat, follow tank preferentially
                    if (followtank)
                    {
                        followtarget = tank;
                        maxDist = 15;
                    }
                }
                else
                {
                    log(Color.Red, "UNKNOWN MODE {0}", mode);
                }

                if (followtarget == null)
                {
                    //log(Color.Violet, "No target in FollowTarget");
                }
                else if (followtarget.IsDead)
                {
                    //log(Color.Violet, "Target is dead in FollowTarget");
                }
                else if (followtarget.DistanceCalc() <= maxDist && followtarget.InLineOfSpellSight)
                {
                    // If close enough, leave
                    WoWMovement.MoveStop();
                    if (followtarget.IsValidUnit() && !StyxWoW.Me.IsSafelyFacing(followtarget))
                        Face(followtarget);
                }
                else
                {
                    // Keep following
                    log("Keep following {0} (dist {1:0.00}, InLineOfSight = {2}, InLineOfSpellSight = {3})", followtarget.SafeName(), followtarget.DistanceCalc(), followtarget.InLineOfSight, followtarget.InLineOfSpellSight);
                    WoWPoint loc = followtarget.Location;

                    // Get to right under the mob, if it's flying
                    if (!Navigator.CanNavigateFully(StyxWoW.Me.Location, followtarget.Location))
                    {
                        //List<float> heights = Navigator.FindHeights(loc.X, loc.Y);
                        //if (heights.Count > 0)
                        //    loc.Z = heights.Max();
                        WoWMovement.ClickToMove(loc);
                    }
                    else
                    {
                        // Move to the mob's location
                        Navigator.MoveTo(loc);
                        //log("loc={0}, lasttargetloc={1}, me.loc = {2}", loc, lasttargetloc, StyxWoW.Me.Location);
                        //if (loc != lasttargetloc)
                        //{
                        //    targetpath = Navigator.GeneratePath(StyxWoW.Me.Location, loc).ToList();
                        //    log("targetpath = {0}, targetpath.count = {1}", targetpath, targetpath.Count);
                        //    lasttargetloc = loc;
                        //}
                        //if (targetpath.Count > 0)
                        //{

                        //    if (StyxWoW.Me.Location.Distance(targetpath[0]) < 3)
                        //    {
                        //        log("Removing first wowpoint in path");
                        //        targetpath.RemoveAt(0);
                        //    }
                        //    if (targetpath.Count > 0)
                        //    {
                        //        log("Setting ctm spot to {0}", targetpath[0]);
                        //        loc = targetpath[0];
                        //    }
                        //}
                        //log("CTM on {0}", loc);
                        //WoWMovement.ClickToMove(loc);

                    }
                    retval = true;
                }

                MyTimer.Stop(fnname);
                return retval;
            }

            public List<WoWUnit> GetAdds()
            {
                string fnname = "MBC.GetAdds";
                MyTimer.Start(fnname);
                WoWUnit pettarget = null;
                WoWUnit mytarget = null;

                // Get pet target
                if (PetInCombat())
                    pettarget = StyxWoW.Me.Pet.CurrentTarget;

                // Get my target
                if (StyxWoW.Me.CurrentTarget != null)
                {
                    WoWUnit target = StyxWoW.Me.CurrentTarget;
                    if (target.IsValidUnit() &&
                        (target.Combat || target.Name == "Training Dummy") &&
                        target.IsDead == false)
                        mytarget = target;
                }

                // Get all eligible units
                List<WoWUnit> allunits = (
                    from u in ObjectManager.GetObjectsOfType<WoWUnit>(false, false)
                    where !u.IsFriendly
                    where !(mode == "@PULL" && u.TaggedByOther == true)
                    where u.Combat
                    where !u.IsDead
                    where u.DistanceSqr < 50 * 50
                    where (!(Battlegrounds.IsInsideBattleground && IgnoreMobsBattleground.Contains(u.Name)))
                    where !u.Mounted
                    where !u.OnTaxi
                    where !u.IsOnTransport
                    select u).ToList();

                // Add current targets
                if (pettarget != null && !allunits.Contains(pettarget)) allunits.Add(pettarget);
                if (mytarget != null && !allunits.Contains(mytarget)) allunits.Add(mytarget);

                if (false)
                {
                    if (allunits.Count > 0)
                    {
                        debug(Color.Magenta, "{0} adds in all units:", allunits.Count);
                        foreach (WoWUnit wu in allunits)
                        {
                            string s = "    ";
                            s += string.Format("level {0} ", wu.Level);
                            s += string.Format("{0} ", wu.SafeName());
                            s += string.Format("health={0} ({1:0}%) ", wu.CurrentHealth, wu.HealthPercent);
                            s += string.Format("dist={0:0.0} ", wu.Distance);
                            s += string.Format("targetweight={0} ", wu.TargetWeight());
                            s += string.Format("crowdcontrolled={0} ", wu.IsDead || wu.IsCrowdControlled());
                            s += string.Format("isplayer={0} ", wu.IsPlayer);
                            s += string.Format("blacklisted={0} ", Blacklist.Contains(wu));
                            debug(Color.Magenta, s);
                        }
                    }
                }

                bool bg = Battlegrounds.IsInsideBattleground;
                // Get preferrred list (allunits but only those attacking us)
                List<WoWUnit> attackingus = (
                    from u in allunits
                    where u.IsValidUnit()
                    where (bg || (u.Combat &&
                    (u.IsTargetingMeOrPet ||
                    u.IsTargetingMyPartyMember ||
                    u.IsTargetingAnyMinion ||
                    u.IsTargetingMyRaidMember ||
                    IsTargetingPartyPets(u)
                     )))
                    select u).ToList();

                // Add current targets
                if (pettarget != null && !attackingus.Contains(pettarget)) attackingus.Add(pettarget);
                if (mytarget != null && !attackingus.Contains(mytarget)) attackingus.Add(mytarget);

                // Try to use preferred list
                List<WoWUnit> units = attackingus;

                // But if it's empty, kill everyone close by.
                if (false)
                {
                    if (StyxWoW.Me.Combat == true && units.Count == 0 && closePeople.Count <= 1)
                    {
                        debug(Color.Yellow, "No adds but I'm in combat, looking at all nearby targets.");
                        units = allunits;
                    }
                }
                units = (from u in units
                         orderby
                             u.TargetWeight() descending,
                             u.IsCrowdControlled() ascending,
                             u.IsAttackingHealer() descending,
                             u.Guid == StyxWoW.Me.CurrentTargetGuid descending,
                             u.Guid == tank.CurrentTargetGuid descending,
                             u.DistanceSqr ascending
                         select u).ToList();

                if (false)
                {
                    if (units.Count > 0)
                    {
                        debug(Color.Yellow, "{0} adds:", units.Count);
                        foreach (WoWUnit wu in units)
                        {
                            string s = "    ";
                            s += string.Format("level {0} ", wu.Level);
                            s += string.Format("{0} ", wu.SafeName());
                            s += string.Format("health={0} ({1:0}%) ", wu.CurrentHealth, wu.HealthPercent);
                            s += string.Format("dist={0:0.0} ", wu.Distance);
                            s += string.Format("targetweight={0} ", wu.TargetWeight());
                            s += string.Format("crowdcontrolled={0} ", wu.IsDead || wu.IsCrowdControlled());
                            s += string.Format("isplayer={0} ", wu.IsPlayer);
                            s += string.Format("blacklisted={0} ", Blacklist.Contains(wu));
                            debug(Color.Yellow, s);
                        }
                    }
                }
                MyTimer.Stop(fnname);
                return units;

            }

            private WoWPlayer GetHealer()
            {
                string fnname = "MBC.GetHealer";
                MyTimer.Start(fnname);
                WoWPlayer player;
                player = (from p in people
                          where p.Role().Contains("Healer")
                          orderby p.DistanceSqr ascending
                          select p).FirstOrDefault();
                MyTimer.Stop(fnname);
                return player;
            }

            private WoWPlayer GetHealTarget()
            {
                string fnname = "MBC.GetHealTarget";
                MyTimer.Start(fnname);
                WoWPlayer healTarget;
                List<WoWPlayer> healTargets = (from p in closePeople
                                               where p.IsDead == false
                                               where p.IsGhost == false
                                               where p.InLineOfSight
                                               where p.HealthPercent < 90
                                               where p.DistanceSqr < 40 * 40
                                               orderby p.HealWeight()
                                               select p).ToList();
                healTarget = healTargets.FirstOrDefault();
                //if (healTargets.Count > 1)
                //{

                //    debug(Color.GreenYellow, "Heal Weights:");
                //    for (int i = 0; i < healTargets.Count; i++)
                //    {
                //        debug(Color.GreenYellow, "  Heal target {0}: {1} weight {2:0} health {3:0} dist {4:0.0}",
                //            i.ToString(),
                //            healTargets[i].SafeName(),
                //            healTargets[i].HealWeight(),
                //            healTargets[i].HealthPercent,
                //            healTargets[i].DistanceCalc());
                //    }


                //}
                MyTimer.Stop(fnname);
                return healTarget;
            }

            private int NumItems(string itemname)
            {
                int cnt = 0;
                string fnname = "MBC.NumItems";
                MyTimer.Start(fnname);
                using (StyxWoW.Memory.AcquireFrame())
                {
                    WoWItem item = StyxWoW.Me.CarriedItems.FirstOrDefault(i => i != null && i.Name == itemname);
                    if (item != null)
                        cnt = Convert.ToInt32(Lua.GetReturnValues(String.Format("return GetItemCount(\"{0}\")", item.Name), "NumItems.lua")[0]);
                }
                MyTimer.Stop(fnname);
                return cnt;
            }

            private WoWItem GetInventoryItem(string origname)
            {
                string fnname = "MBC.GetInventoryItem";
                MyTimer.Start(fnname);
                string name = origname.ToLower();
                foreach (WoWItem item in StyxWoW.Me.CarriedItems)
                {
                    if (item != null && item.Name.ToLower() == name)
                    {
                        MyTimer.Stop(fnname);
                        return item;
                    }
                }
                debug("  Didn't find {0}", name);
                MyTimer.Stop(fnname);
                return null;
            }

            public List<WoWUnit> GetNearbyUnfriendlyUnits()
            {
                String fnname = "MBC.GetNearbyUnfriendlyUnits";
                MyTimer.Start(fnname);
                List<WoWUnit> units;
                units = (from u in ObjectManager.GetObjectsOfType<WoWUnit>(false, false)
                         where u.DistanceSqr < 40 * 40
                         where u.IsValidUnit()
                         //where !Blacklist.Contains(u)
                         where !u.Mounted
                         orderby
                                 u.IsCrowdControlled() ascending,
                                 u.IsAttackingHealer() descending,
                                 u.TargetWeight() descending,
                                 u.Guid == StyxWoW.Me.CurrentTargetGuid descending,
                                 u.Guid == tank.CurrentTargetGuid descending,
                                 u.DistanceSqr ascending
                         select u).ToList();
                MyTimer.Stop(fnname);
                return units;
            }

            private WoWPlayer GetTank()
            {
                string fnname = "MBC.GetTank";
                WoWPlayer tank = null;

                MyTimer.Start(fnname);

                // Return RaFHelper if specified
                if (RaFHelper.Leader != null)
                {
                    tank = RaFHelper.Leader;
                }
                else
                {
                    // Return focused unit if it's a player in my party
                    tank = GetFocusedPlayer();
                    if (tank == null)
                        // Look for the actual tank.
                        tank = (from p in people
                                where p.Role().Contains("Tank")
                                orderby p.DistanceSqr ascending
                                select p).FirstOrDefault();

                    // Look for the leader
                    if (tank == null)
                        tank = (from p in people
                                where p.Role().Contains("Leader")
                                orderby p.DistanceSqr ascending
                                select p).FirstOrDefault();

                    if (tank == null)
                        tank = StyxWoW.Me;

                }
                MyTimer.Stop(fnname);
                return tank;
            }

            private int GetPartyWithAura(string auraname)
            {
                string fnname = "MBC.GetPartyWithAura";
                MyTimer.Start(fnname);
                int count = 0;
                foreach (WoWPlayer p in people)
                {
                    if (p.HasAura(auraname))
                        count += 1;
                }
                MyTimer.Stop(fnname);
                return count;
            }

            private int GetPartyWithHealth(string strHealth)
            {
                int health = Int32.Parse(strHealth);
                return (from p in closePeople
                        where p.IsDead == false
                            && p.HealthPercent <= health
                        select p).Count();
            }

            private bool HasTotem(String totemname)
            {
                foreach (WoWTotemInfo x in StyxWoW.Me.Totems)
                {
                    if (x == null)
                        continue;
                    if (x.WoWTotem == WoWTotem.None)
                        continue;
                    if (x.Name == totemname)
                        return true;
                }
                return false;
            }

            private bool ItemOnCooldown(string itemname)
            {
                bool retval = false;
                string fnname = "MBC.ItemOnCooldown";
                MyTimer.Start(fnname);
                using (StyxWoW.Memory.AcquireFrame())
                {
                    WoWItem item = StyxWoW.Me.CarriedItems.FirstOrDefault(i => i != null && i.Name == itemname);
                    if (item == null)
                    {
                        debug("Don't have {0}", itemname);
                        retval = true;
                    }
                    else
                    {
                        int cnt = Convert.ToInt32(Lua.GetReturnValues(String.Format("return GetItemCount(\"{0}\")", item.Name), "HasItem.lua")[0]);
                        if (cnt == 0)
                        {
                            debug("Have 0 of {0}", item.Name);
                            retval = true;
                        }
                        else if (item.Cooldown > 0)
                        {
                            debug("Item {0} on cooldown. Time left: {1}", item.Name, item.Cooldown);
                            retval = true;
                        }
                    }
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private bool ListAndTakeNewTarget(List<WoWUnit> units)
            {
                string fnname = "MBC.ListAndTakeNewTarget";
                MyTimer.Start(fnname);
                if (units == null || units.Count == 0)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                WoWUnit newtarget = units[0];
                // Leave early if the first unit is the same as the current target
                if (newtarget.Guid == StyxWoW.Me.CurrentTargetGuid)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }
                log(Color.Violet, "Retargeting during combat - taking the first target from the list below.");
                foreach (WoWUnit unit in units)
                {
                    string tag2 = "";
                    if (unit.Guid == newtarget.Guid)
                        tag2 += "+";
                    if (unit.Guid == StyxWoW.Me.CurrentTargetGuid)
                        tag2 += "-";
                    if (unit.Guid == tank.CurrentTargetGuid)
                        tag2 += "t";
                    tag2 = (tag2 + "  ").Substring(0, 2);
                    log(Color.Violet, "    {0} {1,-24} cc={2} atkhlr={3} wgt={4} thr={5} tank={6} inrange={7} dist={8:0.0} atk={9} {10} {11:0}",
                        tag2,
                        unit.SafeName(),
                        unit.IsCrowdControlled() ? 1 : 0,
                        unit.IsAttackingHealer() ? 1 : 0,
                        unit.TargetWeight(),
                        (int)unit.ThreatInfo.ThreatStatus,
                        unit.Guid == tank.CurrentTargetGuid ? 1 : 0,
                        unit.Distance < 5 ? 1 : 0,
                        unit.DistanceCalc(),
                        unit.CurrentTarget != null && unit.CurrentTarget.IsPlayer ? "?" : "",
                        unit.CurrentTarget != null ? unit.CurrentTarget.SafeName() : "",
                        unit.CurrentTarget != null ? unit.CurrentTarget.HealthPercent : 0);
                }
                newtarget.Target();
                Face(newtarget);

                MyTimer.Stop(fnname);
                return true;

            }

            private bool Macro(string macrotext)
            {
                double cooldown = 0;
                int c = macrotext.IndexOf(' ');
                if (c >= 0)
                {
                    string s = macrotext.Substring(0, c);
                    if (Double.TryParse(s, out cooldown))
                        macrotext = macrotext.Substring(c + 1);
                }
                if (OnCooldown(macrotext, cooldown, false))
                    return false;
                SaveCooldown(macrotext);
                log("    Macro: {0}", macrotext);
                Lua.DoString(string.Format("RunMacroText(\"{0}\");", macrotext));
                return false;
            }

            private bool OnCooldown(string spellname)
            {
                return OnCooldown(spellname, 0, false);
            }

            private bool OnCooldown(string spellname, bool debugspell)
            {
                return OnCooldown(spellname, 0, debugspell);
            }

            public static List<String[]> ListMyAuras()
            {
                return ListAuras(StyxWoW.Me);
            }

            public static List<String[]> ListTargetAuras()
            {
                return ListAuras(StyxWoW.Me.CurrentTarget);
            }

            public static List<String[]> ListAuras(WoWUnit target)
            {
                List<String[]> lst = new List<String[]>();
                if (target == null)
                    return lst;
                List<String> keys = target.Auras.Keys.ToList();
                keys.Sort();
                foreach (String key in keys)
                {
                    WoWAura aura = target.Auras[key];
                    String[] arr = { "", "", "" };
                    arr[0] = aura.Name;
                    arr[1] = (aura.StackCount == 0 ? "" : aura.StackCount.ToString());
                    arr[2] = (aura.TimeLeft.TotalSeconds == 0 ? "" : string.Format("{0:0.00}", aura.TimeLeft.TotalSeconds));
                    lst.Add(arr);
                }
                return lst;
            }

            public static List<String[]> ListCooldowns()
            {
                WoWSpell spell = null;
                List<String> keys = spellWasLastCast.Keys.ToList();
                keys.Sort();

                List<String[]> cooldowns = new List<String[]>();
                foreach (String key in keys)
                {
                    String[] arr = new String[4];
                    arr[0] = key;
                    FakeCooldown fc = null;
                    if (fakecooldowns.ContainsKey(key))
                    {
                        fc = fakecooldowns[key];
                        arr[1] = fc.SpellID.ToString();
                    }
                    else
                    {
                        spell = SpellManager.Spells[key];
                        arr[1] = spell.Id.ToString();
                    }
                    arr[2] = string.Format("{0:hhmmss}", spellWasLastCast[key]);
                    double cooldownremaining = 0;
                    if (fc != null)
                        cooldownremaining = spellWasLastCast[key].AddSeconds(fc.Cooldown).Subtract(DateTime.Now).TotalSeconds;
                    else
                        cooldownremaining = spell.CooldownTimeLeft.TotalSeconds;
                    arr[3] = (cooldownremaining <= 0 ? "" : string.Format("{0:0.00}", cooldownremaining));
                    cooldowns.Add(arr);
                }
                return cooldowns;
            }

            private bool OnCooldown(string spellname, double seconds, bool debugspell)
            {
                bool retval = false;
                string fnname = "MBC.OnCooldown";
                MyTimer.Start(fnname);
                Color ds = Color.Magenta;
                WoWSpell spell = null;
                bool WoWCooldown = false;
                bool FTWCooldown = false;
                bool RecentCooldown = false;
                bool ElapsedCooldown = false;
                double elapsedtime = -1;
                String errmsg = "";

                // Get the spell
                if (fakecooldowns.ContainsKey(spellname))
                    spell = WoWSpell.FromId(fakecooldowns[spellname].SpellID);
                else if (SpellManager.Spells.ContainsKey(spellname))
                    spell = SpellManager.Spells[spellname];
                else
                    spell = null;

                // Get the elapsed time since the last cast.
                if (spellWasLastCast.ContainsKey(spellname))
                    elapsedtime = (DateTime.Now - spellWasLastCast[spellname]).Seconds;

                // Check if it's on WoW cooldown
                //if (spell != null)
                //{
                //    if (spell.Cooldown)
                //    {
                //        WoWCooldown = spell.Cooldown;
                //    }
                //else
                //{
                //    // Do a harder check - make sure it's been at least BaseCooldown milliseconds since last cast
                //    // This avoids problems with spells that don't actually cast when you think they do, and eat the rotation.
                //    WoWCooldown = (elapsedtime * 1000) < spell.BaseCooldown;
                //    if (WoWCooldown)
                //        log("Elapsedtime {0} * 1000 < spell.BaseCooldown {1}", elapsedtime, spell.BaseCooldown);
                //}
                //}

                // Check if it's on recent cooldown
                uint cd = 1500;
                if (spell != null)
                    cd = Math.Min(spell.CastTime, 1500);
                RecentCooldown = ((lastspellcastname == spellname) && (DateTime.Now - lastspellcasttime).TotalMilliseconds < cd);

                // Check if it's on FTW cooldown
                if (fakecooldowns.ContainsKey(spellname))
                    FTWCooldown = elapsedtime >= 0 && elapsedtime < fakecooldowns[spellname].Cooldown;
                else if (spell != null)
                    WoWCooldown = spell.Cooldown;

                // Check if it's on elapsed time cooldown
                ElapsedCooldown = elapsedtime > -1 && seconds > 0 && elapsedtime < seconds;

                if (WoWCooldown)
                    errmsg = "WoW";
                else if (RecentCooldown)
                    errmsg = "double-cast";
                else if (FTWCooldown)
                    errmsg = "FTW";
                else if (ElapsedCooldown)
                    errmsg = "timer";

                if (errmsg != "")
                {
                    if (debugspell)
                        if (errmsg == "timer")
                            log(ds, "... {0} on {1} cooldown (elapsedtime {2} seconds {3})", spellname, errmsg, elapsedtime, seconds);
                        else
                            log(ds, "... {0} on {1} cooldown", spellname, errmsg);
                    retval = true;
                }

                MyTimer.Stop(fnname);
                return retval;
            }

            private bool OnPetCooldown(string spellname)
            {
                string fnname = "MBC.OnPetCooldown";
                MyTimer.Start(fnname);
                bool retval = false;
                WoWPetSpell spell = null;
                foreach (WoWPetSpell sp in StyxWoW.Me.PetSpells)
                {
                    if (sp.Spell != null && sp.Spell.Name == spellname)
                    {
                        spell = sp;
                        break;
                    }
                }
                if (spell == null)
                {
                    retval = false;
                }
                else if (spell.Cooldown)
                {
                    retval = true;
                }
                else if (!spellWasLastCast.ContainsKey(spellname))
                {
                    retval = false;
                }
                else
                {
                    int elapsedtime = (DateTime.Now - spellWasLastCast[spellname]).Seconds;
                    if (fakePetCooldownTimes.ContainsKey(spellname) &&
                       (elapsedtime < fakePetCooldownTimes[spellname]))
                        retval = true;
                }
                MyTimer.Stop(fnname);
                return retval;
            }

            private void SaveCooldown(string spellname)
            {
                // Note the last time the spell was cast or action was taken, for the 'fake cooldowns.'
                DateTime dt = DateTime.Now;
                if (spellWasLastCast.ContainsKey(spellname))
                    spellWasLastCast.Remove(spellname);
                spellWasLastCast[spellname] = dt;
                lastspellcastname = spellname;
                lastspellcasttime = dt;
            }

            private bool ScrollLockDown()
            {
                return System.Windows.Forms.Control.IsKeyLocked(System.Windows.Forms.Keys.Scroll);
            }

            private bool On_DumpAuras(WoWUnit target)
            {
                debug(Color.PaleVioletRed, "DUMPAURAS: {0} auras:", target.SafeName());
                foreach (WoWAura aura in target.Auras.Values)
                    debug(Color.PaleVioletRed, "    {0} {1} {2} ms", aura.Name, aura.StackCount, aura.TimeLeft.TotalMilliseconds);
                return false;
            }

            private bool On_DumpParty()
            {
                debug(Color.Yellow, "{0} close party members:", closePeople.Count);
                foreach (WoWPlayer p in closePeople)
                    debug(Color.Yellow, "    {0,-16} {1:00.0} {2:000.0}% ({3:000000} total health)", p.SafeName(), p.Distance, p.HealthPercent, p.MaxHealth);
                debug(Color.Yellow, "Tank is {0}", tank == null ? "<none>" : tank.SafeName());
                debug(Color.Yellow, "Healer is {0}", healer == null ? "<none>" : healer.SafeName());
                return false;
            }

            private int StunCount(WoWPoint loc)
            {
                string fnname = "MBC.StunCount";
                MyTimer.Start(fnname);
                int cnt = (from u in adds
                           where u.IsCrowdControlled()
                           where u.Location.Distance(loc) < 8
                           select u).Count();
                MyTimer.Stop(fnname);
                return cnt;
            }

            private int TotemCount()
            {
                int ctr = 0;
                foreach (WoWTotemInfo x in StyxWoW.Me.Totems)
                {
                    if (x == null)
                        continue;
                    if (x.WoWTotem == WoWTotem.None)
                        continue;
                    ctr += 1;
                }
                return ctr;
            }

            #endregion

        }

        static class AuraWatcher
        {
            private static Dictionary<string, WoWAura> auras = new Dictionary<string, WoWAura>();
            private static int lasthealth = 0;
            private static DateTime lastauraupdate;
            private static DateTime LastCombat = DateTime.Now;

            public static void Pulse()
            {
                int currenthealth = (int)StyxWoW.Me.HealthPercent;
                DateTime currenttime = DateTime.Now;
                double healthchange = currenthealth - lasthealth;
                double elapsedms = (currenttime - lastauraupdate).TotalMilliseconds;

                double r = healthchange * 1000 / elapsedms;
                //if (r < 0)
                //    debug(Color.Orange, "Current health: {0:0.0}, Last health: {1:0.0}, Diff: {2:0.0}, Ms: {3:0.0}, Rate: {4:0.0} ", currenthealth, lasthealth, healthchange, elapsedms, r);

                lasthealth = currenthealth;
                lastauraupdate = currenttime;

                foreach (WoWAura aura in StyxWoW.Me.Auras.Values.ToList())
                {
                    if (!auras.ContainsKey(aura.Name))
                    {
                        if (aura.TimeLeft.TotalSeconds > 0)
                        {
                            if (aura.IsHarmful)
                                debug(Color.Pink, "+++ {0} - {1} stacks, {2:0.0} seconds remaining", aura.Name, aura.StackCount, aura.TimeLeft.TotalSeconds);
                            else
                                debug(Color.LightGreen, "+++ {0} - {1} stacks, {2:0.0} seconds remaining", aura.Name, aura.StackCount, aura.TimeLeft.TotalSeconds);
                            auras[aura.Name] = aura;
                        }
                    }
                }

                foreach (WoWAura aura in auras.Values.ToList())
                {
                    if (!StyxWoW.Me.Auras.ContainsKey(aura.Name))
                    {
                        debug(Color.Gray, "----- {0}", aura.Name);
                        auras.Remove(aura.Name);
                    }
                }
            }
        }

        static class CheckForChest
        {
            private static DateTime nexttime = DateTime.Now;
            private static DateTime stoptime = DateTime.Now;
            private static int pullSeconds = 60;
            private static int blacklistMinutes = 60;
            private static int checkInterval = 5;
            private static ulong lastguid = 0;
            private static Color color = Color.SlateBlue;

            public static void Clear()
            {
                if (BotPoi.Current.Type == PoiType.Loot)
                {
                    BotPoi.Clear();
                    lastguid = 0;
                }
            }
            public static void Pulse()
            {
                string fnname = "CheckForChest.Pulse";
                MyTimer.Start(fnname);
                if (BotPoi.Current.Type == PoiType.Loot)
                {
                    // Black list loot if it's taking too long
                    log(color, "LOOT CHESTS: Moving towards {0} ({1:X}) at {2} yards away", BotPoi.Current.Name, BotPoi.Current.Guid, BotPoi.Current.Location.Distance(StyxWoW.Me.Location));
                    if (BotPoi.Current.Guid == lastguid && DateTime.Now > stoptime)
                    {
                        log(color, "LOOT CHESTS: Blacklisting {0} ({1:X}) - didn't get it after {2} seconds.", BotPoi.Current.Name, BotPoi.Current.Guid, blacklistMinutes);
                        Blacklist.Add(lastguid, BlacklistFlags.All, TimeSpan.FromMinutes(60));
                        Clear();
                    }
                }
                else
                {
                    // Look for new loot
                    if (DateTime.Now < nexttime)
                    {
                        MyTimer.Stop(fnname);
                        return;
                    }
                    // OMG DON'T run for a chest in a dungeon!
                    // 'Tap tool' on beer keg in Stormstout causes you to pull multiple rooms of mobs.
                    if (StyxWoW.Me.IsInInstance)
                        return;
                    nexttime = DateTime.Now.AddSeconds(checkInterval);
                    List<WoWGameObject> chests = (from c in ObjectManager.GetObjectsOfType<WoWGameObject>(false, false)
                                                  where c.Distance < Styx.CommonBot.LootTargeting.LootRadius
                                                  where c.Type != WoWObjectType.Corpse &&
                                                    c.SubType == WoWGameObjectType.Chest &&
                                                    !Blacklist.Contains(c.Guid, BlacklistFlags.All) &&
                                                    c.CanLoot == true
                                                  where c.IsHerb == false && c.IsMineral == false
                                                  orderby c.Distance
                                                  select c).ToList();
                    if (chests.Count > 0)
                    {
                        lastguid = chests[0].Guid;
                        BotPoi.Current = new BotPoi(chests[0], PoiType.Loot);
                        log(color, "LOOT CHESTS: Going after {0} ({1:X}) at {2} yards away", chests[0].Name, chests[0].Guid, chests[0].Distance);
                        stoptime = DateTime.Now.AddSeconds(pullSeconds);
                    }
                }
                MyTimer.Stop(fnname);
            }
        }

        public static class UI
        {
            private static DateTime nextUIupdate = DateTime.Now;
            public static int updateinterval = 100;
            public delegate void UpdateUIDelegate(EventArgs args);
            public static event UpdateUIDelegate UpdateUI;


            public static void Pulse()
            {
                if (DateTime.Now > nextUIupdate)
                {
                    nextUIupdate = DateTime.Now.AddMilliseconds(updateinterval);
                    if (UpdateUI != null)
                        UpdateUI(new EventArgs());
                }
            }
        }

        public static class DungeonWatcher
        {
            public static bool AutoAcceptDungeon = false;
            public static bool AutoQueueDungeon = false;
            private static DateTime dungeondoneat = DateTime.MinValue;

            public static void Initialize()
            {
                // Watch for dungeon invites
                Lua.Events.AttachEvent("LFG_PROPOSAL_SHOW", HandleDungeonInvite);
                Lua.Events.AttachEvent("LFG_COMPLETION_REWARD", HandleDungeonDone);
                Lua.Events.AttachEvent("RESURRECT_REQUEST", HandleResurrect);
            }

            public static void HandleDungeonInvite(object sender, LuaEventArgs args)
            {
                if (AutoAcceptDungeon)
                {
                    log(Color.SandyBrown, "Accepting dungeon invite");
                    Lua.DoString("AcceptProposal()");
                }
            }

            public static void HandleDungeonDone(object sender, LuaEventArgs args)
            {
                // Done with dungeon
                if (AutoQueueDungeon)
                {
                    Thread.Sleep(60000);
                    Lua.DoString("LeaveParty()");
                }
            }

            public static void HandleResurrect(object sender, LuaEventArgs args)
            {
                float waitseconds = Lua.GetReturnVal<float>("return GetCorpseRecoveryDelay()", 0);
                Thread.Sleep((int)(waitseconds * 1000));
                Lua.DoString("AcceptResurrect()");
            }

            public static void Pulse()
            {
                string s = Lua.GetReturnVal<string>("local m,s = GetLFGMode(LE_LFG_CATEGORY_LFD); return m;", 0);
                if (s == null)
                    s = "<null>";
                //debug(Color.SandyBrown, "LFGMode: {0}", s);
                if (StyxWoW.Me.Level < 15 ||
                    AutoQueueDungeon == false ||
                    StyxWoW.Me.IsInInstance == true ||
                    IsLFGDeserter() == true ||
                    InLFDQueue() == true)
                    return;

                log(Color.SandyBrown, "Queuing for dungeon");
                Lua.DoString("LFDQueueFrame_Join()");

            }

            private static bool InLFDQueue()
            {
                string s = GetLFDMode();
                log(Color.SandyBrown, "LFDMode: {0}", s);
                return s == "queued" || s == "proposal";
            }

            private static string GetLFDMode()
            {
                string s = Lua.GetReturnVal<string>("local m,s = GetLFGMode(LE_LFG_CATEGORY_LFD); return m;", 0);
                return s == null ? "none" : s;
            }

            private static bool IsLFGDeserter()
            {
                return Lua.GetReturnVal<bool>("return UnitHasLFGDeserter('player')", 0);
            }

        }

        static class CheckForStuck
        {
            #region Class Variables
            private static Random random = new Random();
            private static WoWPoint stuckPoint = WoWPoint.Empty;
            private static DateTime stuckNextTime = DateTime.Now;
            private static int stuckDistance = 5;
            private static int stuckCtr = 0;
            private static int stuckInterval = 120;
            private static DateTime antiafk = DateTime.Now;
            private static int antiafkinterval = 300000;
            #endregion

            public static void Pulse()
            {
                string fnname = "CheckForStuck.Pulse";

                if (!
                    (StyxWoW.IsInGame &&
                    StyxWoW.Me != null &&
                    StyxWoW.Me.IsValid &&
                    !StyxWoW.Me.IsDead &&
                    !StyxWoW.Me.IsGhost &&
                    !StyxWoW.Me.IsOnTransport &&
                    !StyxWoW.Me.OnTaxi)
                    )
                    return;

                if (!(",Gatherbuddy2,ArchaeologyBuddy,".Contains("," + Styx.CommonBot.BotManager.Current.Name + ",")))
                    return;

                // Stuck logic
                MyTimer.Start(fnname);
                int numJumps;
                if (NotStuck() || DateTime.Now < stuckNextTime)
                {
                    MyTimer.Stop(fnname);
                    if (NotStuck())
                        stuckCtr = 0;
                    return;
                }

                stuckCtr += 1;
                if (stuckCtr < 5)
                {
                    log(Color.Tomato, "Stuck for the {0} time - moving about a bit.", stuckCtr);
                }
                else
                {
                    log(Color.Tomato, "Stuck 5 times - giving up!", stuckCtr);
                    return;
                }

                // Anti-AFK logic
                //if (DateTime.Now > antiafk)
                //{
                //    antiafk = DateTime.Now.AddMilliseconds(antiafkinterval + random.Next(3000, 5000));
                //    KeyboardManager.PressKey((Char)System.Windows.Forms.Keys.Right);
                //    Thread.Sleep(random.Next(10, 20));
                //    KeyboardManager.ReleaseKey((Char)System.Windows.Forms.Keys.Right);
                //}

                // Just press the space bar first
                numJumps = random.Next(1, 3);
                for (int i = 0; i < numJumps; i++)
                {
                    PressSpace();
                    if (NotStuck())
                    {
                        MyTimer.Stop(fnname);
                        return;
                    }
                }

                // Back up
                numJumps = random.Next(1, 3);
                for (int i = 0; i < numJumps; i++)
                {
                    WoWMovement.Move(WoWMovement.MovementDirection.Backwards);
                    Thread.Sleep(random.Next(1000, 2000));
                    WoWMovement.MoveStop();
                    if (NotStuck())
                    {
                        MyTimer.Stop(fnname);
                        return;
                    }
                }

                // Long jumps
                numJumps = random.Next(2, 4);
                for (int i = 0; i < numJumps; i++)
                {
                    KeyboardManager.PressKey((char)Keys.Space);
                    Thread.Sleep(random.Next(2000, 3000));
                    KeyboardManager.ReleaseKey((char)Keys.Space);
                    Thread.Sleep(random.Next(250, 750));
                    if (NotStuck())
                    {
                        MyTimer.Stop(fnname);
                        return;
                    }
                }


                // Up + space
                numJumps = random.Next(1, 3);
                for (int i = 0; i < numJumps; i++)
                {
                    KeyboardManager.PressKey((char)Keys.Up);
                    Thread.Sleep(random.Next(30, 50));
                    KeyboardManager.PressKey((char)Keys.Space);
                    Thread.Sleep(random.Next(500, 750));
                    KeyboardManager.ReleaseKey((char)Keys.Up);
                    KeyboardManager.ReleaseKey((char)Keys.Space);
                    Thread.Sleep(random.Next(250, 750));
                    if (NotStuck())
                    {
                        MyTimer.Stop(fnname);
                        return;
                    }
                }

                // Space + up
                numJumps = random.Next(1, 3);
                for (int i = 0; i < numJumps; i++)
                {
                    KeyboardManager.PressKey((char)Keys.Space);
                    Thread.Sleep(random.Next(30, 50));
                    KeyboardManager.PressKey((char)Keys.Up);
                    Thread.Sleep(random.Next(500, 750));
                    KeyboardManager.ReleaseKey((char)Keys.Up);
                    KeyboardManager.ReleaseKey((char)Keys.Space);
                    Thread.Sleep(random.Next(250, 750));
                    if (NotStuck())
                    {
                        MyTimer.Stop(fnname);
                        return;
                    }
                }
                MyTimer.Stop(fnname);
            }

            private static void PressSpace()
            {
                KeyboardManager.PressKey((char)Keys.Space);
                Thread.Sleep(random.Next(250, 500));
                KeyboardManager.ReleaseKey((char)Keys.Space);
                Thread.Sleep(random.Next(250, 750));
            }

            private static bool NotStuck()
            {
                if (StyxWoW.Me == null ||
                    !StyxWoW.IsInGame ||
                    !StyxWoW.Me.IsValid)
                {
                    return true;
                }

                if (stuckPoint == WoWPoint.Empty || StyxWoW.Me.Location.Distance(stuckPoint) > stuckDistance)
                {
                    stuckPoint = StyxWoW.Me.Location;
                    stuckNextTime = DateTime.Now.AddSeconds(stuckInterval);
                    return true;
                }

                return false;
            }
        }

        static class Evade
        {
            private static ulong PullTargetGuid = 0;
            private static DateTime PullStopTime = DateTime.Now;
            private static int MaxPullTime = 20;
            private static ulong CurrentTargetGuid = 0;
            private static DateTime CurrentTargetStopTime = DateTime.Now;
            private static int MaxCurrentTargetTime = 20;

            public static bool CheckCombat()
            {
                // Check for combat taking too long
                string fnname = "Evade.CheckCombat";
                MyTimer.Start(fnname);
                WoWUnit target = StyxWoW.Me.CurrentTarget;
                if (target == null || target.IsFriendly)
                {
                    MyTimer.Stop(fnname);
                    return true;
                }
                if (target.IsDead == true && !target.IsFriendly)
                {
                    StyxWoW.Me.ClearTarget();
                    return true;
                }

                if (target.Guid != CurrentTargetGuid)
                {
                    // New target
                    CurrentTargetStopTime = DateTime.Now.AddSeconds(MaxCurrentTargetTime);
                    CurrentTargetGuid = target.Guid;
                    log("Starting combat with {0} {1}", target.SafeName(), target.ShortGuid());
                    if (!FightThisWay.mbc.MovementDisabled)
                        WoWMovement.MoveStop();
                }
                else if (DateTime.Now > CurrentTargetStopTime && target.HealthPercent > 99 && !target.Name.Contains("Training Dummy") && !StyxWoW.Me.GroupInfo.IsInParty && !StyxWoW.Me.GroupInfo.IsInRaid)
                {
                    // I've been fighting the same target by myself for too long - blacklist
                    log("Combat with {0} has gone on {1} seconds with no health reduction - adding to blacklist", target.SafeName(), MaxCurrentTargetTime);
                    Blacklist.Add(target.Guid, TimeSpan.FromMinutes(5));
                    StyxWoW.Me.ClearTarget();
                    MyTimer.Stop(fnname);
                    return false;
                }
                MyTimer.Stop(fnname);
                return true;
            }

            public static bool CheckPull()
            {
                string fnname = "Evade.CheckPull";
                MyTimer.Start(fnname);
                // Check for botpoi not being something else
                if (!(BotPoi.Current.Type == PoiType.Kill || BotPoi.Current.Type == PoiType.None))
                    return false;

                // Check for pull taking too long
                WoWUnit target = StyxWoW.Me.CurrentTarget;
                if (target == null || target.IsFriendly)
                {
                    MyTimer.Stop(fnname);
                    return false;
                }

                if (StyxWoW.Me.HealthPercent < 50 || StyxWoW.Me.ManaPercent < 50)
                {
                    log(Color.Red, "Health or mana is below 50%, not pulling!");
                    StyxWoW.Me.ClearTarget();
                    MyTimer.Stop(fnname);
                    return false;
                }

                //if (!target.IsValidUnit())
                //{
                //    log("Blacklisting {0} for 60 seconds in CheckPull", target.SafeName());
                //    Blacklist.Add(target, TimeSpan.FromSeconds(60));
                //    StyxWoW.Me.ClearTarget();
                //    MyTimer.Stop(fnname);
                //    return false;
                //}

                int clustersize = target.ClusterSize();
                log(Color.Violet, "PULL: Target = {0} at {1} ({2}) ({3} mobs surrounding)", target.SafeName(), target.DistanceCalc(), target.FactionId, clustersize);

                if (target.Guid != PullTargetGuid)
                {
                    // New pull target - reset timer for maxPullTime seconds
                    PullStopTime = DateTime.Now.AddSeconds(MaxPullTime);
                    PullTargetGuid = target.Guid;
                }
                else if (DateTime.Now > PullStopTime)
                {
                    // Still pulling after maxPullTime seconds
                    log(Color.Red, "Can't pull {0} after {1} seconds - blacklist for 5 minutes.", target.SafeName(), MaxPullTime);
                    Blacklist.Add(target.Guid, BlacklistFlags.All, TimeSpan.FromMinutes(5));
                    StyxWoW.Me.ClearTarget();
                    MyTimer.Stop(fnname);
                    return false;
                }
                MyTimer.Stop(fnname);
                return true;
            }
        }

    }

    static class MyTimer
    {
        private static Dictionary<string, DateTime> starts = new Dictionary<string, DateTime>();
        private static Dictionary<string, int> count = new Dictionary<string, int>();
        private static Dictionary<string, int> elapseds = new Dictionary<string, int>();
        private static DateTime nexttime = DateTime.Now;

        public static void Pulse()
        {
            if (DateTime.Now < nexttime)
                return;
            nexttime = DateTime.Now.AddSeconds(60);
            //Display();
        }

        public static void Start(string s)
        {
            if (!starts.ContainsKey(s))
                starts.Add(s, DateTime.Now);
            else
                starts[s] = DateTime.Now;
        }

        public static void Stop(string s, bool displaynow = false)
        {
            int elapsedtime = (int)(DateTime.Now - starts[s]).TotalMilliseconds;
            if (!elapseds.ContainsKey(s))
            {
                elapseds.Add(s, elapsedtime);
                count.Add(s, 1);
            }
            else
            {
                elapseds[s] += elapsedtime;
                count[s] += 1;
            }
            if (displaynow && elapsedtime > 0)
            {
                FightThisWay.debug(Color.SeaShell, string.Format("-------------------- {0} ({1}) {2}", s, elapsedtime, BotPoi.Current.ToString()));
            }
        }

        public static void Display()
        {
            Color c = Color.Yellow;
            List<KeyValuePair<string, int>> list = elapseds.ToList();
            list.Sort(
                delegate(KeyValuePair<string, int> p1, KeyValuePair<string, int> p2)
                {
                    int compareDate = p2.Value.CompareTo(p1.Value);
                    if (compareDate == 0)
                        return p1.Key.CompareTo(p2.Key); ;
                    return compareDate;
                }
            );
            //FightThisWay.debug(c, "Timings:");
            //int total = 0;
            //foreach (KeyValuePair<string, int> kvp in list)
            //    total += kvp.Value;
            //foreach (KeyValuePair<string, int> kvp in list)
            //    FightThisWay.debug(c, "    {0}: total time {1} ms, ({2:0.0}%), loops {3}, avgtime {4:0} ms", kvp.Key, kvp.Value, kvp.Value * 100 / total, count[kvp.Key], kvp.Value / count[kvp.Key]);
        }

    }

    public static class FTWExtensionMethods
    {
        #region Private variables
        private static int _petstate = 0;
        private static DateTime _pettimer = DateTime.Now;
        private static Dictionary<string, int> killweights = new Dictionary<string, int> {
            // Healers
            {"Druid_talentless", 4}, 
            {"Priest_talentless", 4}, 
            {"Paladin_talentless", 4},
            {"Monk_talentless", 4},
            {"Shaman_talentless", 4},
            // Casters
            {"Mage_talentless", 3},
            {"Warlock_talentless", 3},
            {"Hunter_talentless", 2},
            // Melee
            {"Rogue_talentless", 2},
            {"DeathKnight_talentless", 2},
            {"Warrior_Talentless", 2}};
        #endregion

        #region WoWPlayer extension methods

        public static bool ClearBehindMe(this WoWPlayer p, string strRange)
        {
            float range = float.Parse(strRange);
            WoWPoint pt = WoWMathHelper.CalculatePointBehind(p.Location, p.Rotation, -range);
            if (!Navigator.CanNavigateFully(StyxWoW.Me.Location, pt))
                return false;
            List<WoWUnit> list = (from u in ObjectManager.GetObjectsOfType<WoWUnit>(false, false)
                                  where u.Location.DistanceSqr(pt) < range * range
                                  where u.IsDead == false
                                  where u.IsHostile == true
                                  select u).ToList();
            if (list.Count > 0)
                return false;
            return true;
        }

        public static int UnitPower(this WoWPlayer p, int powertype)
        {
            string fnname = "FTWExtensionMethods.UnitPower";
            MyTimer.Start(fnname);
            int cnt = 0;
            try
            {
                cnt = Convert.ToInt32(Lua.GetReturnValues(string.Format("return UnitPower('player', {0})", powertype), "PowerType.lua")[0]);
            }
            catch (Exception ex)
            {
                FightThisWay.log(Color.Red, "Error when getting UnitPower - {0}", ex.Message);
                cnt = 0;
            }
            MyTimer.Stop(fnname);
            return cnt;
        }

        public static bool HasSpell(this WoWPlayer p, String spellname)
        {
            string fnname = "FTWExtensionMethods.HasSpell";
            MyTimer.Start(fnname);
            WoWSpell spell = null;
            try
            {
                spell = SpellManager.Spells[spellname];
            }
            catch (KeyNotFoundException ex)
            {
            }
            MyTimer.Stop(fnname);
            if (spell == null)
                return false;
            else
                return true;
        }

        public static string Role(this WoWPlayer p)
        {
            string fnname = "FTWExtensionMethods.Role";
            MyTimer.Start(fnname);
            IEnumerable<WoWPartyMember> wpms = StyxWoW.Me.GroupInfo.RaidMembers.Union(StyxWoW.Me.GroupInfo.PartyMembers).Distinct();
            WoWPartyMember wpm = (from member in wpms
                                  let x = member.ToPlayer()
                                  where x != null && p.Guid == x.Guid
                                  select member).FirstOrDefault();
            MyTimer.Stop(fnname);
            if (wpm == null)
                return "";
            else
                return wpm.Role.ToString();
        }

        public static bool IsDPS(this WoWPlayer p)
        {
            if (!StyxWoW.Me.GroupInfo.IsInParty && !StyxWoW.Me.GroupInfo.IsInRaid)
                return true;
            return p.Role().Contains("Damage");
        }

        public static bool IsTank(this WoWPlayer p)
        {
            if (!StyxWoW.Me.GroupInfo.IsInParty && !StyxWoW.Me.GroupInfo.IsInRaid)
                return true;
            return p.Role().Contains("Tank");
        }

        public static bool IsHealer(this WoWPlayer p)
        {
            if (!StyxWoW.Me.GroupInfo.IsInParty && !StyxWoW.Me.GroupInfo.IsInRaid)
                return false;
            return p.Role().Contains("Healer");
        }

        public static bool HasItem(this WoWPlayer p, string itemname)
        {
            bool retval;
            string fnname = "FTWExtensionMethods.HasItem";
            MyTimer.Start(fnname);
            using (StyxWoW.Memory.AcquireFrame())
            {
                WoWItem item = StyxWoW.Me.CarriedItems.FirstOrDefault(i => i != null && i.Name == itemname);
                if (item == null)
                {
                    retval = false;
                }
                else
                {
                    int cnt = Convert.ToInt32(Lua.GetReturnValues(String.Format("return GetItemCount(\"{0}\")", item.Name), "HasItem.lua")[0]);
                    if (cnt == 0)
                        retval = false;
                    else
                        retval = true;
                }
            }
            MyTimer.Stop(fnname);
            return retval;
        }

        public static bool EatingOrDrinking(this WoWPlayer p)
        {
            if (p.Combat) return false;
            if (p.HasAura("Drink") && p.ManaPercent < 95) return true;
            if (p.HasAura("Food") && p.HealthPercent < 95) return true;
            return false;
        }

        public static bool EligibleForCleanse(this WoWPlayer p)
        {
            string fnname = "FTWExtensionMethods.EligibleForCleanse";
            MyTimer.Start(fnname);
            bool retval = !p.IsDead && !p.IsGhost && p.InLineOfSight && p.HealthPercent < 90 && p.IsDiseased();
            MyTimer.Stop(fnname);
            return retval;
        }

        public static bool EligibleForRevive(this WoWPlayer p)
        {
            string fnname = "FTWExtensionMethods.EligibleForRevive";
            MyTimer.Start(fnname);
            bool retval = p.IsDead || p.IsGhost;
            MyTimer.Stop(fnname);
            return retval;
        }

        public static string ClassAndSpec(this WoWPlayer p)
        {
            string fnname = "FTWExtensionMethods.ClassAndSpec";
            MyTimer.Start(fnname);
            string s = "";
            using (StyxWoW.Memory.AcquireFrame())
            {
                string _myclass = p.Class.ToString();
                string _myspec = "talentless";
                // Get class
                int talentTabId = Lua.GetReturnVal<int>("return GetSpecialization()", 0); // Returns 1 to 3 for primary talent tab id, or 0 if no talent tree chosen yet.
                if (talentTabId != 0)
                {
                    // Get spec
                    var GetTalentTabInfo = Lua.GetReturnValues(string.Format("return GetSpecializationInfo({0},false,false)", talentTabId));
                    _myspec = GetTalentTabInfo[1].Replace(" ", "_");
                }
                s = string.Format("{0}_{1}", _myclass, _myspec);
            }
            MyTimer.Stop(fnname);
            return s;
        }

        public static int HealWeight(this WoWPlayer p)
        {
            string s = p.Role();
            int adj = 0;
            if (s == "Tank")
                adj = 5;
            else if (s == "Healer")
                adj = 10;
            return (int)p.HealthPercent - adj;
        }

        public static int PetIsMissing(this WoWPlayer p)
        {
            if (p.Mounted)
                return 0;

            if (p.GotAlivePet && p.Pet != null)
            {
                if (_petstate != 0)
                    FTW.FightThisWay.log("Got pet again, resetting flag. Hair is no longer on fire.");
                _petstate = 0;
                return 0;
            }
            if (_petstate == 0)
            {
                FTW.FightThisWay.log("Might have a missing pet, setting timer.");
                _pettimer = DateTime.Now.AddSeconds(5);
                _petstate = 1;
                return 0;
            }
            else if (_petstate == 1)
            {
                FTW.FightThisWay.log("Waiting 3 seconds for pet to appear.");
                if (DateTime.Now < _pettimer)
                    return 0;
                _petstate = 2;
                return 0;
            }
            else if (_petstate == 2)
            {
                FTW.FightThisWay.log("Pet still not here.");
                _petstate = 3;
                return 0;
            }
            else
            {
                // petstate == 3 - let revive pet kick in
                FTW.FightThisWay.log("OK, pet is gone - resummon it.");
                return 1;
            }
        }

        #endregion

        #region WoWUnit extension methods

        public static int ClusterSize(this WoWUnit unit)
        {
            string fnname = "FTWExtensionMethods.ClusterSize";
            MyTimer.Start(fnname);
            List<WoWUnit> units = (from u in ObjectManager.GetObjectsOfType<WoWUnit>(false, false)
                                   where u.Guid != unit.Guid
                                   where u.Location.Distance(unit.Location) < 6
                                   where u.CanSelect || u.Attackable
                                   where !u.IsFriendly
                                   where !u.IsDead
                                   where !u.IsPet
                                   where !u.IsNonCombatPet
                                   where !u.IsCritter
                                   where !u.Mounted
                                   select u).ToList();
            MyTimer.Stop(fnname);
            return (units.Count);
        }

        public static string ShortGuid(this WoWUnit unit)
        {
            return unit.Guid.ToString().Substring(unit.Guid.ToString().Length - 4, 4);
        }

        public static int AuraExpiring(this WoWUnit unit, string auraname)
        {
            string fnname = "FTWExtensionMethods.AuraExpiring";
            MyTimer.Start(fnname);

            // Return my aura if it exists, first.
            foreach (WoWAura aura in unit.Auras.Values)
            {
                if (aura.Name == auraname && aura.CreatorGuid == StyxWoW.Me.Guid)
                {
                    MyTimer.Stop(fnname);
                    return (int)aura.TimeLeft.TotalMilliseconds;
                }
            }

            // Otherwise any aura of this name
            foreach (WoWAura aura in unit.Auras.Values)
            {
                if (aura.Name == auraname)
                {
                    MyTimer.Stop(fnname);
                    return (int)aura.TimeLeft.TotalMilliseconds;
                }
            }

            // No aura found
            MyTimer.Stop(fnname);
            return 0;
        }

        public static int MyAuraExpiring(this WoWUnit unit, string auraname)
        {
            string fnname = "FTWExtensionMethods.MyAuraExpiring";
            MyTimer.Start(fnname);
            foreach (WoWAura aura in unit.Auras.Values)
            {
                if (aura.Name == auraname && aura.CreatorGuid == StyxWoW.Me.Guid)
                {
                    MyTimer.Stop(fnname);
                    return (int)aura.TimeLeft.TotalMilliseconds;
                }
            }
            MyTimer.Stop(fnname);
            return 0;
        }

        public static bool HasMyAura(this WoWUnit unit, string auraname)
        {
            string fnname = "FTWExtensionMethods.HasMyAura";
            MyTimer.Start(fnname);
            foreach (WoWAura aura in unit.Auras.Values)
            {
                if (aura.Name == auraname && aura.CreatorGuid == StyxWoW.Me.Guid)
                {
                    MyTimer.Stop(fnname);
                    return true;
                }
            }
            MyTimer.Stop(fnname);
            return false;
        }

        public static int StackCount(this WoWUnit unit, string auraname)
        {
            // Return my aura if it exists, first.
            string fnname = "FTWExtensionMethods.StackCount";
            MyTimer.Start(fnname);
            foreach (WoWAura aura in unit.Auras.Values)
            {
                if (aura.Name == auraname && aura.CreatorGuid == StyxWoW.Me.Guid)
                {
                    MyTimer.Stop(fnname);
                    return (int)aura.StackCount;
                }
            }

            // Otherwise any aura of this name
            foreach (WoWAura aura in unit.Auras.Values)
            {
                if (aura.Name == auraname)
                {
                    MyTimer.Stop(fnname);
                    return (int)aura.StackCount;
                }
            }

            // No aura found
            MyTimer.Stop(fnname);
            return 0;
        }

        public static int MyStackCount(this WoWUnit unit, string auraname)
        {
            string fnname = "FTWExtensionMethods.MyStackCount";
            MyTimer.Start(fnname);
            foreach (WoWAura aura in unit.Auras.Values)
            {
                if (aura.Name == auraname && aura.CreatorGuid == StyxWoW.Me.Guid)
                {
                    MyTimer.Stop(fnname);
                    return (int)aura.StackCount;
                }
            }
            MyTimer.Stop(fnname);
            return 0;
        }

        public static string SafeName(this WoWUnit unit)
        {
            if (unit.IsPlayer)
            {
                WoWPlayer player = (WoWPlayer)unit;
                if (player == StyxWoW.Me)
                    return "Me";
                string prefix = "";
                if (player.IsInMyPartyOrRaid)
                    prefix = "Party";
                return string.Format("{0}{1}_{2}", prefix, player.Class.ToString(), player.ShortGuid());
            }
            else
                return unit.Name;
        }

        public static int TargetWeight(this WoWUnit unit)
        {
            if (unit.IsPlayer)
            {
                WoWPlayer player = (WoWPlayer)unit;
                string cs = player.ClassAndSpec();
                if (killweights.ContainsKey(cs))
                    return killweights[cs];
                else
                    return 1;
            }
            if (!FightThisWay.MobWeights.ContainsKey(unit.Name))
                return 0;
            else
                return FightThisWay.MobWeights[unit.Name];
        }

        public static bool IsCrowdControlled(this WoWUnit unit)
        {
            string fnname = "FTWExtensionMethods.IsCrowdControlled";
            bool retval = false;
            MyTimer.Start(fnname);
            if (unit == null)
            {
                MyTimer.Stop(fnname);
                return false;
            }
            foreach (WoWAura aura in unit.Debuffs.Values)
            {
                try
                {
                    if (FightThisWay.Stuns.Contains(aura.Name))
                        retval = true;
                    else if (aura.Spell == null)
                        continue;
                    else if ((
                         aura.Spell.Mechanic == WoWSpellMechanic.Asleep ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Banished ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Frozen ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Horrified ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Invulnerable2 ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Incapacitated ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Polymorphed ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Rooted ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Sapped ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Shackled ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Stunned ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Turned ||
                         aura.Spell.Mechanic == WoWSpellMechanic.Fleeing)
                        && aura.TimeLeft.TotalMilliseconds > 0)
                        retval = true;
                }
                catch (Exception ex)
                {
                    FightThisWay.log(Color.Red, "Error when examining aura {0} : {1}", aura.Name, ex.Message);
                    retval = false;
                }
                if (retval == true)
                    break;
            }
            if (retval)
                FightThisWay.log(Color.GreenYellow, "{0} IS INCAPACITATED", unit.SafeName());
            MyTimer.Stop(fnname);
            return retval;
        }

        public static string CurrentSpellName(this WoWUnit unit)
        {
            if (unit == null ||
                unit.CurrentCastId <= 0 ||
                (!unit.IsCasting && !unit.IsChanneling))
                return string.Empty;
            WoWSpell spell = WoWSpell.FromId((int)unit.CurrentCastId);
            if (spell == null)
                return string.Empty;
            return spell.Name;
        }

        public static bool IsValidUnit(this WoWUnit unit)
        {
            string fnname = "FTWExtensionMethods.IsValidUnit";
            if (unit == null) return false;
            MyTimer.Start(fnname);
            bool retval = true;
            string ispet = unit.IsPet.ToString();
            string owner = "<none>";
            string reason = "";

            if (Blacklist.Contains(unit.Guid))
            {
                if (unit.Combat && (
                    unit.IsTargetingMeOrPet ||
                    unit.IsTargetingMyPartyMember ||
                    unit.IsTargetingAnyMinion ||
                    unit.IsTargetingMyRaidMember ||
                    FightThisWay.mbc.IsTargetingPartyPets(unit)
                    ))
                {
                    // ignore  blacklist
                    // HB will keep feeding you that target anyway.
                }
                else
                {
                    reason = "{0} is blacklisted";
                }
            }
            else if (!unit.CanSelect || !unit.Attackable) reason = "can't select or attack {0}";
            else if (unit.IsFriendly) reason = "{0} is friendly";
            else if (unit.IsDead) reason = "{0} is dead";
            // The following line causes you to ignore mobs your party is fighting.
            //else if (unit.TaggedByOther) reason = "{0} is tagged by other.";
            else if (unit.HealthPercent == 0) reason = "{0}'s health is 0";
            else if ((unit.IsPet && ((unit.OwnedByUnit != null && unit.OwnedByUnit.IsPlayer == true) || unit.OwnedByRoot == null || unit.OwnedByRoot.IsDead == false))) reason = "{0} is pet";
            else if (unit.IsNonCombatPet) reason = "{0} is noncombat pet";
            else if (unit.IsCritter) reason = "{0} is critter";
            else if (Battlegrounds.IsInsideBattleground && FightThisWay.IgnoreMobsBattleground.Contains(unit.Name)) reason = "{0} is in ignored battleground mobs";
            MyTimer.Stop(fnname);
            if (reason == "")
            {
                retval = true;
                if (false && unit.IsPet)
                {
                    if (unit.OwnedByRoot != null)
                    {
                        string prefix = "npc";
                        if (unit.OwnedByRoot.IsPlayer)
                            prefix = "player";
                        owner = string.Format("pet {0} owned by {1} {2}", unit.Name, prefix, unit.OwnedByRoot.Name);
                    }
                    else
                    {
                        owner = string.Format("pet {0} owned by no one", unit.Name);
                    }
                    FightThisWay.debug(Color.MediumVioletRed, owner);
                }
            }
            else
            {
                FightThisWay.debug(Color.MediumVioletRed, reason, unit.Name);
                retval = false;
            }
            return retval;
        }

        public static bool HasAuraWithMechanic(this WoWUnit unit, params WoWSpellMechanic[] mechanics)
        {
            string fnname = "FTWExtensionMethods.HasAuraWithMechanic";
            MyTimer.Start(fnname);
            foreach (KeyValuePair<string, WoWAura> kvp in unit.Auras)
            {
                foreach (WoWSpellMechanic mech in mechanics)
                {
                    if (kvp.Value.Spell.Mechanic == mech)
                    {
                        MyTimer.Stop(fnname);
                        return true;
                    }
                }
            }
            MyTimer.Stop(fnname);
            return false;
        }

        public static bool IsAttackingHealer(this WoWUnit unit)
        {
            string fnname = "FTWExtensionMethods.IsAttackingHealer";
            MyTimer.Start(fnname);
            bool retval = false;
            if (!(unit.IsAlive && unit.IsHostile && unit.CurrentTarget != null)) retval = false;
            else if (!unit.CurrentTarget.IsPlayer) retval = false;
            else if (!unit.IsTargetingMyPartyMember) retval = false;
            else if (!unit.IsTargetingMyRaidMember) retval = false;
            else
            {
                WoWPlayer target = (WoWPlayer)unit.CurrentTarget;
                if (target.Role().Contains("Healer"))
                    retval = true;
            }
            MyTimer.Stop(fnname);
            return retval;
        }

        public static double DistanceCalc(this WoWUnit unit)
        {
            if (unit == null)
                return 0;
            return unit.Distance;
            //return unit.Location.Distance(StyxWoW.Me.Location);
        }

        public static bool IsDiseased(this WoWUnit unit)
        {
            string fnname = "FTWExtensionMethods.IsDiseased";
            MyTimer.Start(fnname);
            if (unit == null)
            {
                MyTimer.Stop(fnname);
                return false;
            }
            WoWAura aura = (from debuff in unit.Debuffs.Values
                            where debuff.Spell != null && FightThisWay.MyBaseClass.dispels.Contains(debuff.Spell.DispelType)
                            select debuff).FirstOrDefault();
            MyTimer.Stop(fnname);
            return aura != null;
        }

        #endregion

        #region WoWSpell extension methods
        public static bool IsAreaSpell(this WoWSpell spell)
        {
            if (FightThisWay.AOESpells.Contains(spell.Name))
                return true;
            else
                return false;
        }
        public static bool IsTankOnlySpell(this WoWSpell spell)
        {
            if (FightThisWay.TankOnlySpells.Contains(spell.Name))
                return true;
            else
                return false;
        }

        //public static bool OnCooldown(this WoWSpell spell)
        //{
        //    var retval = Lua.GetReturnValues(String.Format("return GetSpellCooldown({0})", spell.Id), "SpellCooldown.lua");

        //    if (retval == null || retval.Count < 2)
        //    {
        //        FightThisWay.log(Color.Red, "Error when getting cooldown of {0}!", spell.Name);
        //        return false;
        //    }

        //    float cooldown = float.Parse(retval[1]);
        //    FightThisWay.log("Cooldown remaining for {0}: {1} {2} {3}", spell.Name, retval[0], retval[1], retval[2]);
        //    if (cooldown > 0)
        //        return true;
        //    return false;
        //}
        #endregion
    }
}
